B r u n o B y Design

Magic with grids

A group of colorful squares arranged in a grid.
Posted 3 days ago
Aug 14, 2024

The other day I was watching Michelle Barker's talk on CSS Day 2022 where she showed a neat trick with grid and content length selectors.

Turns out you can use the :has() selector to do a lot of cool things! In her examples, Michelle shows how to create a grid layout that adapts to the length of the content.

She starts with a pretty simple 3 column grid layout:

.grid {
  --cols: 3;
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: 1rem;
}

Pretty simple, right? But what if we want to add one more element to the grid?

That's a bit awkward, isn't it? Now let's introduce the :has() selector to the mix:

.grid:has(:last-child:nth-child(even)) {
  --cols: 2;
}

Now the grid adapts to the content length! Pretty cool, right?

How does it work?

Let's look at the different parts of the selector:

  • :last-child targets the last child of the parent element.
  • :nth-child(even) targets every even child of the parent element.

So effectively, we're targeting the last child and it must be even. Next we add the :has() selector to the mix:

  • :has() targets the parent element that contains the specified selector.

Combining all of these, we're targeting the parent element that contains a even last child.

Now that we know how to target length-based selectors, we can do a lot of cool layouts with this technique.

More examples

Let's expand on this technique and create a few more examples.

2 column grid with hero

.grid:has(:last-child:nth-child(5)) {
  --cols: 2;
}
.grid:has(:last-child:nth-child(5)) .square:first-child {
  grid-column: span 2;
}

3 column grid with 2 hero elements

.grid:has(:last-child:nth-child(6)) {
  --cols: 3;
}
.grid:has(:last-child:nth-child(6)) .square:first-child {
  grid-column: span 3;
}
.grid:has(:last-child:nth-child(6)) .square:nth-child(2) {
  grid-column: span 2;
}

3 column grid with hero elements at start and end

.grid:has(:last-child:nth-child(7)) {
  --cols: 3;
}
.grid:has(:last-child:nth-child(7)) .square:first-child {
  grid-column: span 2;
}
.grid:has(:last-child:nth-child(7)) .square:last-child {
  grid-column: span 2;
}

1 column grid with hero element

.grid:has(:last-child:nth-child(2)):has(:first-child.hero) {
  --cols: 1;
}
.grid:has(:last-child:nth-child(2)) .square.hero {
  grid-row: span 2;
}

2 column grid with hero element

.grid:has(:last-child:nth-child(3)):has(:first-child.hero) {
  --cols: 2;
}
.grid:has(:last-child:nth-child(3)) .square.hero {
  grid-column: span 2;
}

And that's it! I hope this has peaked your curiosity and you'll try out this technique and come up with some cool layouts!

Reading resources