CSS “Quantity Queries” are a thing now

by Matt Fantinel
18 Jun 2024 - 4 min read

I was recently working on some form styling for a project and the design had this example of a group of checkboxes that spread out in 3 columns:

Screenshot of a grid of checkboxes with 3 columns and 3 rows

That's pretty simple to do, right? Just add display: grid to the parent and set grid-template-columns: 1fr 1fr 1fr.

However, things don't look so good when there's only 3 checkboxes:

Screenshot of a grid of checkboxes with 3 columns and a single row

When there's only a few choices, they look better and are easier to read if displayed vertically. So, if there are only 3 items, ideally grid-template-columns would only define one column. But how can we hot-swap between these rules without doing some hacky server-side/JS tweak?

:has() to the rescue

The :has() CSS selector has been available on all major browsers since December 2023 (Firefox was the last to implement it), and is often called the "Parent selector". With it, you're able to select an element based on what it contains. For example, you can select every p element with some bold text (strong) inside by doing p:has(strong). That's already quite useful, but the neat thing is that you can use more complex selectors inside it as well.

We have the :nth-child selector that usually allows us to select every nth element and then do something with it. So, let's say that in our checkbox example above, we want to make the color of the fifth checkbox red. We could do something like:

css
.group .checkbox:nth-child(5) {
	color: red;
}

But what if we could mix both? That :nth-child selector would only return something if there is an element at index 5... so, if we put that inside a :has selector, we could select the parent of that 5th item instead... Eureka!

css
.group:has(.checkbox:nth-child(5)) {
	/*
		Styles here will only apply if a 5th checkbox exists inside .group
		i.e. there are > 5 checkboxes
	*/
}

Amazing! So, with that, we're able to do something like:

css
.group {
	display: grid;
	grid-template-columns: 1fr
}

.group:has(.checkbox:nth-child(5)) {
	/*
		If it has at least 5 checkboxes, make it 2 columns
	*/
	grid-template-columns: 1fr 1fr;
}

.group:has(.checkbox:nth-child(7)) {
	/*
		And 3 columns if there's at least 7 checkboxes
	*/
	grid-template-columns: 1fr 1fr 1fr;
}

Progressive Enhancement

(I really like talking about progressive enhancement)

So, there's a very real possibility that some of your users won't be able to see it as you intended because their browser does not support :has yet. It's sad, but it's true. This is where you have to make a decision. In those cases where :has isn't supported, what do you want to happen?

  • Do things look broken? Is the UX greatly affected? If yes, then you will have to figure out an alternative for those specific users. You can use @supports selector(:has(*)) (for when browser supports it) and @supports not selector(:has(*)) (for when it doesn't), and apply fallback styles accordingly. In that checkbox columns example, perhaps a good fallback would be just default to 2 columns instead.
  • If nothing looks broken, just not ideal, then you can safely leave it as is. No errors will be thrown if you use :has and the browser doesn't know what it is. In that checkbox example, I believe nothing would look broken if I just kept it at 1 column. Then, if a user's browser supports it, the website gets enhanced.

Wrapping up

So, just to wrap up that checkboxes example, you can also sprinkle some container queries in there to make everything responsive! Here was my end result (you can check it out on CodePen as well:

css
.group {
	display: grid;
	grid-template-columns: 1fr
}

.group:has(.checkbox:nth-child(5)) {
	/*
		If it has at least 5 checkboxes, make it 2 columns
  
	    But only if there's enough space for it
	*/
  @container (min-width: 350px) {
	  grid-template-columns: 1fr 1fr;
  }
}

.group:has(.checkbox:nth-child(7)) {
	/*
		And 3 columns if there's at least 7 checkboxes
	*/
  
    @container (min-width: 600px) {
    	grid-template-columns: 1fr 1fr 1fr;
    }
}

Written by

Matt Fantinel

I’m a web developer trying to figure out this weird thing called the internet. I write about development, the web, games, music, and whatever else I feel like writing about!

About

Newsletter? I have it!

I have a newsletter as another way to share what I write on this blog. The main goal is to be able to reach people who don't use RSS or just prefer getting their articles delivered straight into their inbox.

  • No fixed frequency, but at least once at month;
  • I do not plan on having content exclusive to the newsletter. Everything on it will also be on the blog;
  • I will never, ever ever ever, send you any form of spam.

You might also like

View blog

Fantinel.dev v5 is here!

10 min read

Out with the green waves, in with the rainbow of pastel colors!

Meta
Read

Setting up Storybook on an Astro project

7 min read

I really, really thought this was gonna be easy.

Front-End
Read

text-overflow: ellipsis on multi-line text

2 min read

Adding an automatic ellipsis to some text if it overflows its content is simple enough. But what if you want your text to have multiple lines?

Front-End
Read

Automating Social Media Preview Images

6 min read

Social media preview images are very useful if you want to attract people to your website. They're sometimes a pain to create, though. Let's automate it!

Front-End
Read