I :has() an idea about lists

Regrets, dear reader, but this new CSS feature required an ancient meme reference.

This was originally published on .

While I was rewriting this site's CSS, I wanted to find something that could benefit from the new :has() pseudo-class, without inventing a need for it.

Friend, that something is lists.

I love lists. Ordered, unordered, definition, I don't care, you have a set of things, you need a list. One thing that's bothered me about lists--specifically of the un/ordered variety, is that they can become pretty long and scroll-y.

And with :has(), we can wrangle those lengthy lists pretty easily.

.smarter-list {
  columns: var(--listColumns);

.smarter-list:has(:nth-child(n+21)) {
  --listColumns: 2;

.smarter-list:has(:nth-child(n+41)) {
  --listColumns: 3;

What's going on here? First, any .smarter-list should use columns for its layout. Great, that's reasonable, but how many? By default, none, as --listColumns is undefined without a default, and the browser moves on with its life. But if the .smarter-list has twenty-one child elements, it should use a two-column layout. Why twenty-one? Because I arbitrarily chose twenty as my per-column limit, so the presence of a twenty-first child covers children 21-40. And for more than forty? Double the limit, add one, and boom, three columns.

Here's a CodePen if that's helpful.

I think this pattern is helpful because it keeps the number of classes in my markup to a minimum, and it doesn't require processing in a template to determine which classes to apply based on how many items are in a given list. For this site specifically, I'd have to either do a RegEx test on the raw Markdown in my posts, or do a similar test on the compiled HTML. Put simply, it's overhead I don't want to deal with.

Now, :has() does not have universal support yet, so what's a front-end maker like yourself supposed to do about that? CSS Grid can do the same thing, but with slightly more complex selectors.

.smarter-list {
  display: grid;
  grid-template-columns: repeat(3, auto);
  grid-auto-flow: column;

.smarter-list :nth-child(n+1) ~ * {
  grid-column: 1;

.smarter-list :nth-child(n+20) ~ * {
  grid-column: 2;

.smarter-list :nth-child(n+40) ~ * {
  grid-column: 3;

This works much the same way as the :has() method, except that the column assignment is happening "manually" on the sibling elements of the twentieth and fortieth .smarter-list children. This boils down to the difference between CSS Grid and CSS Columns--along with Floats and Flexbox, a selection of layout tools in our kits, equal in their value and beauty.

Here's another CodePen in these trying times.

For me and my strong opinions tightly held, I prefer to use single-class selectors to keep specificity low, and by my math, both methods here result in the same specificity (20), but using :has() also lets me keep nesting out of my selectors.

My ideal CSS selector is a single class, with psuedo-classes/elements added as necessary. I like to avoid nesting in general, because I like to keep my specificity as low as possible. By my math, both .smarter-lists methods result in the same specificity (20). I think I prefer the :has() method as it lets me live my unnested fantasy, and ultimately require less code. But also? C'mon, it's the new and shiny!