Can you reason out the difference between a selector doing :has(:not) vs :not(:has)? Kilian Valkhof wrote up that post I just linked to. I like how he teaches the way to think it out is to write out all the implied selectors at work. For instance:

.card:has(:not(img)) {}
.card:not(:has(img)) {}Code language: CSS (css)

is really:

.card:has(*:not(img)) {}
.card:not(.card:has(img)) {}Code language: CSS (css)

The latter becomes a lot more clear to me:

  1. Select all the stuff in a .card that isn’t an img.
  2. Select the card as long as it doesn’t contain an img.

Super different. I love stuff like this. Like Sudoku puzzles for CSS nerds.


Speaking of :has(), Heydon gets a bit philosophical with a “Sidebar” layout, explaining how it can get a lot more flexible when we think of how to handle it with any particular layout that just so happens to have a sidebar. And just so happens is a specialty of :has(), polished up with a nice dollop of @container.


I didn’t know this was coming, but I love it, and it makes intuitive sense to me. Una explains in Range Syntax for Style Queries.

/* Range query for rain percent (new) */
@container style(--rain-percent > 45%) {
  .weather-card {
    background: linear-gradient(140deg, skyblue, lightblue);
  }
}Code language: CSS (css)

My brain goes right now how line-height often needs to get sucked in when the font-size goes up. So assuming those are custom properties powered, something like this could be cool:

@container style(--font-size > 2rem) {
  line-height: 1;
}Code language: CSS (css)

But my enthusiasm is tempered by the fact that you can’t do a container query on the same element you set the custom property on. So like this doesn’t work:

h1, h2, h3 {
  font-size: var(--header-font-size, 1.5rem);
  @container style(--header-font-size > 2rem) {
    line-height: 1;
  }
}

h1 {
  --header-font-size: 3rem;
}Code language: CSS (css)

You can see what I mean here with a working version.

Because I’m setting --header-font-size at the same “level” as I’m testing it with @container it just doesn’t work. It would have to be set at least one DOM level higher, which is probably not what I’d be doing on an element like this. Oh well, it’s still cool.


I appreciate Roma Komarov’s obsessing about a tiny detail here. This website Ink & Switch has these kinda rough hand-drawn underlines on it. But when they wrap the wrapping edge is “hard cut”. But it needn’t be so. The CSS property box-decoration-break: clone fixes it entirely. Beautiful. You can kinda forgive someone not knowing this as the browser support isn’t perfect. And more, the fact that it starts with box- kinda makes you think it’s gonna be related to box-shadow but really it’s (get this): border-radius, border-image, box-shadow, and background. That’s some good bar trivia right there for you and all your CSS buds.


My toxic trait is that I like to declare winners of things. Like how Apple won with grid-lanes 😬. Or how oklch() is the winning modern color format. Instead of giving those topics the nuance they deserve. I also have a lot of muscle memory for just using rem units everywhere, considering them the winning unit. But that one is almost certainly too narrow of an opinion. In fact, it’s probably not any one particular unit, but as Miriam says The Best CSS Unit Might Be a Combination: We don’t have to choose between px and rem for spacing.

.card {
  /* use 1lh, unless we run out of space */
  --min: min(1lh, 2vi);

  /* round-up to the nearest half-line */
  --nearest-half: round(up, 2vi, 0.5lh);
}Code language: CSS (css)