#btconf Düsseldorf, Germany 17 - 18 Apr 2023

Michelle Barker

A self-described CSS enthusiast, Michelle is author of front-end blog CSS { In Real Life }, where she aims to share some of the wonderful things we can do with CSS (and web development in general) from the perspective of someone working with it every day. As a Senior Front End Developer at Ada Mode, Michelle is passionate about harnessing our web development superpowers to make a positive impact on the world. With a background in illustration, she enjoys building creative demos, as well as helping developers fall in love with CSS through her technical writing for Smashing Magazine, Codrops, CSS Tricks and others.

Want to watch this video on YouTube directly? This way, please.

Modern CSS Layout is Awesome!

2022 was an amazing year for CSS, bringing us a whole host of features to help us solve common layout challenges. In this session we’ll delve into some real-world use cases for container queries, subgrid, the :has() pseudo class (or parent selector) and much more. We’ll explore how to combine these new features with some more familiar ones, in order to build robust, flexible and creative layouts that respond to both content and context.


[playful music]

[audience applause]

Michelle Barker: [Laughter] Ah... Well, thank you for that introduction, Marc. I’m really excited to be here, and it’s been great so far to see all these talks about things that people are really excited and passionate about, like art and psychology. Now I’m going to talk about something that I’m really excited about, which is putting rectangles inside other rectangles.

[Audience laughter]

Michelle: [Laughter] Because modern CSS layout is awesome, and you know it’s awesome because there’s an exclamation mark at the end of it, so that’s how you know it’s awesome. [Laughter]

But yeah, I really do think it’s awesome. CSS in 2023 has been undergoing a bit of a revolution in the past couple of years, and we have so many more new features coming to CSS. At the moment, it seems like they’re coming all the time. Really, some of these have the potential to change how we think about building layouts for the Web.

I think that’s pretty exciting, and a lot of these new features that have come out in the past couple of years are already well supported and we can already start using them today. In this talk, we’re going to explore a few practical and creative use cases for some of these new features which help us build layouts suitable for an era where how we browse the Web is more flexible than ever.

CSS layout is one of my favorite topics to speak about because it historically has not always been so awesome, as we know. [Laughter] When I started my journey as a Web developer, I came from more of a design background where you know if you want a two-column layout, you just draw some boxes and put the things in the two columns.

Then I came to Web development, and it was like, “Oh, so you have to do some floats and some clear fixes. What the hell is a clear fix?” Nobody really knows. And lots of horrible, hacky stuff just to get a very basic layout on the Web.

But then along came Flexbox, and that made things that had historically been pretty hard suddenly become fairly easy. It’s like, yeah, cool. We can do a two-column layout. That’s nice.

Then grid came along a few years later. To me, that felt like the way we should be building layouts for the Web. As a designer, that made sense to me completely. Now I feel very at home in CSS layout land.

But you know CSS is constantly gaining all these new superpowers, so we’ve kind of overshot easy a little bit. Now it’s like, wow, we have all this CSS layout power but maybe how do we use that. It can actually be a little bit overwhelming, all these new features coming through if you’re not deeply immersed in the world of CSS layout.

Hopefully, that’s where this talk will help a little bit. We’re going to showcase some of these new features, particularly the ones that have been supported in the past year or two and demonstrates ways in which we can use them practically.

A lot of these features are things that developers have been asking for, for a really long time. They worked best when we embrace the flexibility of the Web. We stop chasing pixel perfection. And instead of trying to prescribe exactly how a layout should render at so many different sizes or different browsing conditions, actually just give the browser some hints and let it make informed choices based on content and context.

We’re going to start with a few of the sort of smaller features that might have slipped under the radar a little bit but I think still have the potential to really help us when building our layouts. The first one of these is aspect ratio, which I just love the aspect ratio property because, with a single line of CSS, it eliminates the need for a whole lot of hacky code.

On the left is basically the simplest way to set an aspect ratio on an element before 2021. We used to call this the padding hat because it had a pseudo element with some boxes and padding. Then you absolute position a child of that element. It’s just really horrible, hacky code. No one wants to have to write that.

But now, this is all we need. We just need this one line of CSS on the right there. That makes me very happy. Yay! Cheers at aspect ratio. [Laughter]

It works great with our flex and grid layouts too, especially if we combine it with object-fit when working with images, for instance. Each of these, the items in this grid, has an aspect ratio of one, making it the square. By the way, we can use the previous syntax of two numbers separated by a slash, or we can use a decimal, so 3/2 would be equivalent to aspect ratio 1.5. Both of those are valid.

Then we’re setting object-fit to cover on the images so that whatever the dimensions of our original image, they’ll cover our square items. Or if we have something like a logo grid, we could set object-fit to contain. Ten our logos would be nicely centered inside these squares without getting cropped at all.

The other great thing about aspect ratio is it acts like it’s a minimum or suggested aspect ratio. This box has an aspect ratio of 3/2. But if we have some longer text content in there, then that box is just going to grow to accommodate that text. It’s a little bit more like setting a min height on that box rather than hardcoding a height and saying this has to be aspect ratio 3/2. That’s quite nice.

It’s also possible to set an aspect ratio on an entire grid. Each of these grids has an aspect ratio of 3/2. Then when I’m placing items into this grid, I’m using FR tracks for the rows and columns so that however many FR tracks (fraction unit with CSS grid, which means they’ll take up the proportion of the available space).

As you put more items into these grids, it’s going to create more rows but all constrained within the aspect ratio of that grid. That could be quite nice for a thumbnail gallery or something like that where you have a limited number of items.

Now the next feature I want to demonstrate it the gap property for Flexbox. Grid has had gap for quite a while. We use it to space out a row and column tracks, control the space between them. But it’s relatively new for Flexbox, so let’s take a quick look at the possible use for that.

We have some breadcrumbs, fairly common components which are built with Flexbox. We’re using flex wrap so that when the viewport gets narrower, they’ll just start wrapping onto the next line.

We’re probably going to space those out using margins or maybe some padding. You could set a right-hand margin on each of those flex items, which is all well and good.

Then as they wrap onto the next line, you can see we have a problem, which is that we don’t have any space in between them. They’re just kind of crashing into each other.

Of course, we’re going to set some bottom margin on each of those flex items, which again is fine but then we’re left with this extra space at the bottom of our component because we have to add a bottom margin to everything. We don’t know at what point our items are going to wrap onto the next line.

I think that’s not great. We have to control for that space in the rest of our layout, so maybe move the thing that’s going to be below it up a little bit. To me, that should exist in isolation. I shouldn’t have to do that.

Instead, we use the gap property, and that means that those gaps are only going to happen in between our items. We’re not going to be left with that horrible extra space. Yeah, fairly small change, but I think pretty helpful.

Unfortunately, though, there is no way to detect browser support for gap in Flexbox at the moment because grid has had that gap property for quite a while. If you’re using a future query, it’s only going to detect whether a browser supports the gap property. It’s not going to know whether it supports it in grid or in Flexbox.

You maybe have to be a little bit careful there. It’s a progressive enhancement. Flex gap is pretty well supported now, so it might be that you can use it fairly confidently.

Now we also have some new and improved viewport units to play with. Hopefully, we all agree that viewport units are awesome. I use them all the time. For a while in CSS, we’ve been able to size things relative to the height or width of the viewport using VW (viewport width), VH (viewport height), and then vmin and vmax (which resolve to the smaller or the larger of the two).

Maybe we want a hero section. We want that to be 100% fill the viewport height, so we’re going to use 100VH for that.

But on mobile devices, 100VH (or VH, in general) can mean different things because it’s influenced by the presence of the dynamic toolbars that slide into and out of view depending on whether the user is scrolling. That causes us some problems for our layouts.

Maybe you have some important content at the bottom of your 100VH section, but that’s actually going to be hidden by those dynamic toolbars at certain points, like when it’s at the top of the viewport. Or maybe like this call to action button. Maybe it’s not that important. But then there are some more kind of critical situations where you maybe have some navigation at the bottom of your screen.

We’ve probably all been on a site where you try to click “reject cookies” but you can’t because the button is under the toolbar, which is not so fun.

Recently, I was browsing a site where I was reading quite a long article on a site. What I noticed was that the font size kept changing depending on whether I was scrolling or not, which wasn’t such a problem to begin with. But as I got about two-thirds of the way through the article, and then I needed to scroll up a little bit just to re-read the previous paragraph, those toolbars came into view and the font size changed, meaning that I was jumped to a completely different place in the article.

I think that’s because the font size was using -- well, I guess they were probably using vmax or VH for some responsive font sizing. But that meant, yeah, I was jumped to a completely different place. So, using viewport units here is not always that reliable.

There are some solutions which are these lovely new viewport units, so SVH (small viewport height) will resolve to the small viewport size, so that’s the viewport size with those dynamic toolbars in place. LVH will resolve to the large viewport size, so that’s the height with the toolbars retracted. Small and large viewport height are fixed sizes, so they’ll stay the same unless you actually resize the viewport.

Then DVH is dynamic viewport height. This will actually change depending on whether the toolbars are visible or not, so they’ll change as these toolbars expand or retract, which might be useful for some situations.

You do need to be a little bit careful because the resizing isn’t smooth. It’s throttled, so you might still get a bit of a kind of jumping effect on your page. But it could be useful.

Now so far, these kinds of mobile browser issues are the only practical use I come across for these new viewport units. We can’t really do anything with them on desktop. They don’t take into account, say, the width of the scrollbar using VW units. But that’s not to say they won’t in the future. In the meantime, they do solve a few problems on mobile for us.

Now how often do we want to place one element on top of another in CSS? For me, it’s pretty often. I do that all the time. And this is probably how we’re going to do it, or at least historically.

I’ve got this image. I want to overlay a caption on top of the image. It’s got a blend mode in there so we can see the image bleeding through. And this caption is in pink here.

I’m going to absolute position that and then use top right, bottom left on my figcaption or you could use it some different ways. You could do top right, width 100%, height 100% - any of those kind of variations. But I think this is too much CSS to write, and I like to be lazy with my CSS, so I’m going to use the inset property, which is basically shorthand for top right, bottom left. That means we don’t have to write all that extra CSS, which is very nice.

Inset is, as I said, shorthand, so we can use it in exactly the same way as margin or padding shorthand by setting different values. But we also have logical property variants. Inset block will inset this element on the block axis which (if we are using the left-to-right or right-to-left writing mode) will be the vertical axis and inset inline will set it on the horizontal axis. If we’re using a different writing mode like a vertical writing mode, then those will be switched around.

But there is another way we can place items on top of each other, which doesn’t involve absolute positioning, and that is using grid. We can make our entire figure a grid and then place all direct children of that grid into one grid cell. They’ll just be stacked on top of each other.

This solution has some things to recommend it, so you might notice one difference here is that actually we can still see the padding on our grid. Our parent grid element, the figure, has some padding on it. Everything is being placed into that first grid cell, which means actually that padding will be maintained. It’s not actually entirely covering the element, which might be the thing you want; it might not.

But the other thing is if we put some extra text into that fig caption longer than the available space, then that entire grid cell or that grid row is going to grow to accommodate it. So, we’re not going to get overflow. I think that’s quite a nice way of positioning items on top of each other.

Now we also have new intrinsic sizing keywords that help us size elements according to their content. For this, I’m going to hop on over to the browser so that we can see those in action. So, bear with me.

Cool. It worked the first time.

[audience cheers]

Michelle: [Laughter] You can see we have this box here, which I’ve hardcoded a width of 300 pixels. Obviously, the text is overflowing that box because our box is too small for it.

The second box, we have a width of min content, so that’s going to be sized to accommodate the smallest content size, which is, if we have this block of text, it’s going to be the longest word in that text string, which works well for us here when we’ve just got this quite short text string.

Then we have max-content, which is kind of going to do the opposite. That is going to be sized to the longest possible value for our text. At the moment, that’s not so bad. We just have these three words.

I’m just going to add some extra text in here so you can see what that’s doing. Yep, now you can see all of our text is getting put on one line and our box is the size of our entire paragraph of text.

Hmm... You know maybe not what we want in this scenario, but maybe useful elsewhere.

I’ll just get rid of that for a moment. Then we go to fit content. On first glance, this is the same as max-content. It’s going to be sized to the longest possible value in our text string. But then if we resize the browser, you can see that is actually going to reduce because it’s only going to be sized to that length when there is room to accommodate it. Otherwise, it’s going to just come down until it hits the min-content size, which is the length of the longest word there.

That’s quite handy kind of doing... You know sometimes we do this with display inline block. But here we don’t actually have to do that. We don’t have to make something like an inline-block element in order to size it to the content.

Now I’m going to talk about one of the bigger, maybe more interesting features, or one of the ones with potentially more implication for our layouts. I’m going to start with Subgrid.

Now Subgrid is part of the level two grid specification. Hopefully, if you’re a developer, you’ll have been using CSS grid. We’ve had it for a number of years now.

But Firefox has also had Subgrid for quite a while now, and now it seems like other browsers are starting to catch up. I think Safari now has Subgrid. Chrome is working on it. I thought we were getting it last year, but that didn’t happen. Hopefully, this year. I’m keeping my fingers crossed.

Let’s have a look at what Subgrid can help us with (with our layouts). Subgrid allows any of a grid’s children to inherit the grid of the parent. We have these two cards here with different content in the top, the middle, and the bottom sections. But we want those sections to align horizontally with each other.

Now, without Subgrid, neither card is aware of the other one’s content. So, how do we do that? Well, we could make each card span three rows of the original grid. Then we use Subgrid so that they inherit the parent grid.

We use the grid template rows property with a value of Subgrid, and we can actually override the gap value here as well inside the Subgrid if we chose to. Imagine we might have multiple rows of these cards above and below these ones. We’re probably going to use a gap on that grid to maintain the space between those cards. But inside the card itself, we maybe don’t need it, so we can set that to zero.

Now, Subgrid doesn’t have great browser support at the moment. As I mentioned, Chrome and Edge don’t support it.

Now, in this case, all that’s going to happen if a browser doesn’t support Subgrid is those cards aren’t going to look quite as harmonious. But a user can still read and understand that content perfectly well, so maybe that’s not a big deal. But there’ll probably be some situations where that might not work quite as well, and you need to provide a fallback, which you can do with a feature query, so writing your code for non-supporting browsers above and then inside this @supports block (that’s where you write your code for the browsers that do support Subgrid).

Now that’s the kind of most common or obvious use case. But there are some other cases where it could help us as well. So, in this grid component, we want this fig caption to align with the heading. And this is how our grid looks if we inspect it, and Firefox allows us to inspect subgrids as well as the parent grids, so this is the grid inspector I’m using for this.

The problem is the fig caption is a direct child of the figure. It’s not a direct child of the parent grid. But we need it to align to the same grid as its parent and be aware of that other content so that if that text is longer on the right-hand side there, it’s not going to collide with the caption.

We can set a Subgrid on the figure and this time we’re using both the row and column access. I’m using the grid template (shorthand for grid template rows and grid template columns). Then we can position the image and the caption, fig caption, on the Subgrid because it’s a direct there, both direct children of that figure.

Now if the text content is longer on the right, it’s not going to collide with our fig caption. That whole row is just going to get taller.

Now we’ve already been talking about responsive layouts on the kind of micro component scale. Now let’s look a bit more broadly at a page layout.

Now we all know the most common way to build responsive layouts is with media queries. But over the years, we’ve become more aware of some of their shortcomings.

Suppose we have this grid of three columns. We’ve got these cards here. Maybe we want to take this grid and put it into a narrower space like a sidebar. Now we obviously don’t want that grid to still have three columns. We want it to stack like it does on mobile. But a media query based on the viewport size is not going to detect that our grid is in this narrower space.

Let’s look at a couple of ways we could deal with that. Again, I’m going to hop on over to the browser.

We have two copies of our grid here. We have one in the main content area on the left, the white content area, and then one in the sidebar. You can see our grid at the moment just has one column. When it’s in a larger area, then those cards are going to be massive, which is definitely not what we want.

Let’s, first of all, make this into a two-column grid. We’re going to use the grid template columns property and the repeat function, so we’re going to say let’s make our grid two columns. Each of those will be 1FR, so they’ll take up an equal proportion of the available space.

Now straightaway our left-hand grid looks much better. Our right-hand grid, not so much. We definitely don’t want it to do that.

Instead of repeat two, we’re going to say repeat autofill. That’s going to create as many columns as will fill the available space. At that moment, that’s just one column because we’re still using 1FR. But instead of 1FR for our tracks, we’re going to use grid min-max function.

We’re going to give this a minimum and maximum track size. Let’s say 300 pixels for our minimum, 1FR for the maximum. Now we have this grid which goes to, well, it’s fully responsive. Right? We have one column and two columns and three columns, which is very cool.

But there is a slight problem here, which you might spot on the right-hand side, where our grid items are actually overflowing the container, the spacing around the uniform because 300 pixels is our minimum track size. When they’re in a content area that has less than amount of space, they’re just going to overflow. They’re not going to resize.

Instead of 300 pixels, we’re going to use another function, which is the min function. We can give a min function two or more values and it will pick the smallest one.

Am I doing this right? Putting in another bracket.

audience member: [Indiscernible]

Michelle: Oh, you’re right. Thank you. Thank you. [Laughter]

There we go, so we give two or more values; it will pick the smallest one. If 300 pixels is less than 100%, it will pick 300 pixels. Otherwise, it will pick 100%.

Straightaway, you can see on the right; our grid items are now perfectly sitting in the pace, taking up no more than 100% of the available space. Yeah, it’s fully responsive, so pretty awesome. Right?

We could stop there. We could say, “Hey, that’s enough. Our grid is responsive.” And sometimes this might be all you need, but I can think of one or two problems with this.

First of all, I don’t think this is a very intuitive way to build a responsive grid. I think somebody coming in new to this codebase who isn’t familiar with CSS to such a high degree, perhaps, is going to have a hard time figuring out what’s going on with this layout. How would you even Google that? I don’t know. Maybe you put it into ChatGPT or something. [Laughter]

But okay, you can overcome that with code comments, perhaps, or just developer education. But the other problem is we actually don’t have that much control over this grid. We can’t go from, say, a two-column layout to a four-column layout. We have to have every step in between.

Thirdly, we actually don’t know at what point our grid is going to change the number of columns. If we want to change some other styling, depending on that number of columns, we can’t unless we do some really complex calculations, which kind of defeats the object of this.

I think we can improve upon that. The way we can improve on that is by instead of querying viewport width, we can query at the width of a parent element or container (otherwise known as container queries).

[audience cheers]

Michelle: Yay! Yes, container queries are something I think we’ve all wanted for a really long time and it’s really exciting to see them finally delivered in CSS.

Let’s rewrite this with container queries. We’re going to set a container on the main and the side elements, so the main is the white area on the left. The side is the gray sidebar on the right. We’re going to use the container property, which is shorthand for container name and container type.

The name is optional. We could just use the container-type property. But I find it really helpful to give our container a name because we might have multiple containers. We have nested containers. It’s really helpful to know which one you’re querying.

The type is inline size, which is pretty much the only value you want at the moment. Inline size is equivalent to the width (if you’re using left-to-right or right-to-left writing mode).

The other option is size, which I haven’t found a practical use case for that at the moment. Maybe someone can enlighten me. You can’t query the block size at the moment.

We’re going to use inline size to query the width because that’s what we need. Then we can write our container query, which is a lot like writing a media query, except we use @container, our container’s name, and we’re going to say if the inline size is greater than 700 pixels, do something here.

This is actually the new media query syntax, which is valid in media queries as well. Instead of saying min-width, we’re saying if our inline size is greater than. That means it’s kind of logical property friendly if we’re maybe querying the other axis if we’re in a different writing mode. That’s kind of nice.

It is valid in media queries, as I say. I’m not sure if it’s supported everywhere. I think the last time I checked, maybe Safari didn’t yet support it. But that’s probably changed by now because that was a few months ago.

Now let’s just check if our container query is working by giving this a red background. Yay! Yes, it’s working. Of course, we don’t want to give it a red background. We’re going to use our grid template, columns, property, and we’re going to give our grid two columns. Back to our nice, simple, repeat function.

Now our grid is just going to go from one column to two columns. Of course, you can add some more columns if you want to.

audience member: [Indiscernible]

Michelle: Thank you. Now, at this sort of size, our cards still look kind of big and gross. I think it would be quite nice if when we get to this sort of size, our cards had a more horizontal layout.

Let’s set another container, which needs to be on the parent of our cards. That’s actually the list item in this grid. Let’s just give them a border so that we can see where our containers are.

There we go. You can see each container that we’re referring to. Then we can style the card itself. Let’s do some copying and pasting just to make things nice and speedy.

We’re going to call this container “card,” so we have our container. Now let’s write our container query for our card. Let’s say when our inline size is greater than 450 pixels, let’s make it a two-column layout for our cards using grid. You could use Flexbox. I’m using grid here because why not.

We’ll just say they’re both going to be just 1FR. Now we have this kind of horizontal layout. You can see when we get to the larger sizes, we go back to the vertical layout. Of course, you probably will want to tweak those values a bit so that it looks good.

I’m just going to make that... Let’s make that 500 so that it doesn’t do it too early. Yeah, there we go. Okay, so that’s working.

But also, I think we could still make some more improvements here. In our wider cards, it would probably look a bit better if we had a bit more padding around that text, maybe just increase the font size a little bit. I think that would improve them a little bit.

Let’s style. First of all, let’s do the padding around that text. This card content element.

As we saw a little bit in Scott’s talk yesterday, we can use a clamp function. We can do this kind of viewport relative padding. I’m going to give it three values: 1rem, 2vw, 3rem. What the clamp function will do is it takes three values and it will pick the middle one.

If we have 1rem, 2rems, or 3rems, it’s obviously always going to pick 2rems. That’s always going to be the middle one. But the magic comes in when we give it a dynamic value like a viewport unit because that’s going to change depending on the size of our viewport.

In this case, what it means is that 1rem and 3rems will act as locks here. It’s never going to be smaller than 1rem. It’s never going to be bigger than 3rems. But in between, those viewport sizes in between, it will scale.

You can see that’s working. We have a bit more padding. But it’s relative to the viewport, so it’s the same for both our cards. Instead, again, as we saw in Scott’s talk, we can use container units.

Let’s use CQI (query container inline). Let’s bump that up a little bit. Use 5CQI. You can see the cards on the left. That’s going to give us a bit more padding between our containers are wider.

We can do the same thing with font size as well. I’m not going to go into as much detail as Scott did and do this kind of amazing, interpolated sizing. But let’s do it at a very simple level using the clamp function. Let’s bring that down to like a maximum of, say, 1.6, 4CQI.

Obviously, tweak these values until your heart is content. You can see that size is changing relative to the container now.

Now, we’re not quite done because there’s one more thing you can see here. If we scroll down to the bottom of our container, we can see we have an odd number of items in our two-column layout, which makes me a bit sad, this one little guy hanging out by himself at the bottom. I quite like my grids to be more symmetrical looking. I don’t like to have these leftover grid items on their own.

What we could do is when there is an odd number of items in our grid, let’s make the first item span two columns. We’re going to do that inside our container query here, our layout container query. And we are going to use the :has() pseudo class or parent selector.

We’re going to say this is where it has a potential to all go wrong because all it takes is one typo, right? When our grid has a direct last child, which is also an odd number, so let’s check if this is working, first of all. Give it good old background red. Yep, so our grid on the left has an odd number of items. We know that. This is when it’s over 700 pixels wide. But we don’t want to style the grid. We want to style the first child.

When the direct first child of our grid is of an odd number, let’s use the grid column property to make it span two columns. There we get--

[audience applause]

Michelle: Because we’ve done all that lovely container-based sizing and layout, that’s automatically going to be a lovely horizontal grid item and start looking kind of beautiful.

There we go. That is container queries. That’s how you can use them.

Let’s jump on back over to the presentation for a moment.

Container-relative units, we have a CQW and CQH (query container width, query container height) which basically look similar to VW and VH. Then CQI and CQB (query container inline size and block size), they are the logical property equivalents. Then cqmin and cqmax, which work a lot like vmin and vmax, will resolve to the smaller or the larger of those two values.

You can use container queries today because they’re supported in all major browsers.

[audience cheers and applause]

Michelle: Of course, not everyone has the latest browsers, so it’s worth looking at this polyfill by the Chrome team. If you do know that you need to support older browsers and you want to consider uses of those older browsers, you can maybe think about using this. You can find that on GitHub or NPM.

It’s super-easy to use. It also works with container units as well. Before container queries were supported, I did a very similar demo to that and it works perfectly with this polyfill.

Now that we saw a little bit of what :has() can do, it’s known as the parent selector. But actually, that’s kind of misleading because I think it’s a lot more powerful than that. It allows us to style a parent or a sibling or even more from that, as we’re going to see in a moment.

Here’s a simple-ish example. We could show a different layout for a component depending on whether a certain element is present or not. If we have this element with a block quote and with this SVG icon thing, we might position these on a grid like this. And the text is center aligned in the block quotes.

But then if our grid has an image, then we can actually position all those very differently. We could change up our layouts quite a lot. We don’t have to do the whole thing of, like, appending a different class to each component if it’s a different variant. We can just use :has().

Now we can also use :has(), which is something I like doing, with animated grid tracks. We now have animated grid tracks well-supported everywhere. Again, Firefox had these for quite a while, I think. But now all the other browsers, I think, have them now.

When we hover over an item in this grid, we’re actually changing the grid template columns property of the grid itself. Here we’re saying when you hover on the third child of this grid, change the grid template columns property like this. Here’s how we might actually use that in practice, having this kind of expanding Grid Layout thing.

But the other thing is, yeah, it’s not just a parent selector, and it’s not even just a sibling selector. We can reach down to any element pretty much anywhere on our page and then reach up to the parent and then down to another element somewhere else, like the cousin of our selector, providing that somewhere along the line they share a common ancestor, which they all do. They all share the body tag.

Here I’m changing my grid depending on hovering on something completely outside of my grid. It’s kind of cool.

There’s just one more demo that I want to show you, which is combining pretty much everything in the last few demos. We’ve got this sort of responsive layout with container queries. Then we have these animated grid tracks here.

I’m hovering. I’m going to open up this link here. That panel slides out because it’s using those animated grid tracks. It’s animating the grid template columns property. And you can see that our layout adjusts as well because it’s using container queries.

None of this is done with JavaScript. That’s just opening that grid, changing that grid template columns property because the link target is inside one of our grid tracks.

Now, I don’t recommend you do this because it almost definitely has accessibility issues. But I just think it’s a pretty interesting example of the power of :has() and the power of what CSS can do now. I’m just enjoying playing around with that stuff.

Back to the presentation.

That’s a demo.

Yes, that’s it. [Laughter]

[audience applause]