Halvor William Sanden

From 15 lines of JS to 3 lines of CSS has()

Using CSS has() to write complex selectors is fun, especially when we compare the logic to JavaScript and can see how different the two languages are at doing the same.

A colleague and I recently made two buttons to expand and collapse all the levels in a documentation schema, and we didn’t want to show the two buttons in cases where there were no expanding levels. We needed conditional checking for the existence of an element.

Solutions #

We placed the button pair in a toolbar above the schema container. The ideal solution could have been to render the buttons only when the schema needed them. That would require a lot of lines and time to write the Go template code we use to loop each media type and schema for the check and then do it once more for the content. And since most of the schemas would display the buttons anyway, it was not worth the effort.

JavaScript #

We wrote JavaScript that checked for the existence of an expandable button, then used the parent element of that (schema container), selected its previous sibling (schema toolbar) and removed a utility class that hid the button pair.

function showToggleAll() {
	const schemaContainerArr = document.querySelectorAll('.schema__container')
	if (schemaContainerArr) {
		schemaContainerArr.forEach((schemaContainer) => {
			const schemaToggles = schemaContainer.querySelectorAll('.param-toggle-btn')
			if (schemaToggles.length > 0) {
				const schemaToolbar = schemaContainer.previousElementSibling,
					toggleAllPair = schemaToolbar.querySelectorAll('button[data-toggle-all]')
				toggleAllPair.forEach((toggleAllBtn) => {


We did the same with CSS has() by writing a selector that selects each toolbar that has a sibling following it (`+`) with the class `schema__container`, which contains a collapsible (as indicated by `param-toggle-btn`). Within that toolbar, we set `display: flex` on any button with the data attribute `toggle-all`.

.schema__toolbar:has(+.schema__container .param-toggle-btn) button[data-toggle-all] {
	display: flex;

Even though JS removes a class while CSS overwrites, the two have the same functionality. As of early 2023, Firefox will still lack support for has() for a few months, so we decided to keep both solutions for a while.

With this use of CSS, should we use different CSS classes for styling and purely selecting?