The problem#
A common use of JavaScript is to change which content is displayed on a web page. In some simple cases this can actually be done without JavaScript. While there's some older articles on how to do this, some newer HTML/CSS features can help.
The solution#
The simplest case is the <details>
tag which allows
showing a foldable section without even requiring any CSS. But for more
complicated cases, variations on the "Checkbox Hack"
can allow CSS to express more complicated rules about what to show/hide.
The examples in that article all rely on the content being revealed
being a sibling of the <input>
element defining the checkbox. That's
less restrictive than it sounds because the <label>
element that's
the actual visible part of the checkbox can be placed anywhere. But
even that restriction can be loosened using :has()
as shown
in this example simplified from the HTML/CSS used by my previous
post:
<div class="filters">
<label>
<input type="checkbox" id="show_completed">
Show Completed Tasks
</label>
</div>
<div class="todo_list">
<ul>
<li class="completed">some task</li>
</ul>
</div>
.completed {
display: none;
}
.filters:has(#show_completed:checked) ~ .todo_list .completed {
display: list-item;
}
The details#
How it works#
The basic concept is that we the CSS display
property can
be changed to show/hide content. And we can give it different values
based on selectors that depend on :checked
or not to make
a checkbox control the visibility. The complexity comes from needing
to write a CSS selector relating the elements we want to control the
visibility of to the checkbox element.
~
subsequent-sibling combinator#
The examples in the "Checkbox Hack" post and as I've
done this before rely on the checkbox and the element to
hide being siblings and using the subsequent-sibling combinator ~
to select that element:
<input id="show_completed" type="checkbox">
<li class="completed">(some content)</li>
#show_completed:checked ~ .completed {
display: list-item;
}
Combining more selectors we can allow different structures, but the
<input>
's location is still constrained relative to the element to
hide:
<input id="show_completed" type="checkbox">
<ul>
<li class="completed">(some content)</li>
</ul>
#show_completed:checked ~ ul .completed {
display: list-item;
}
:has()
pseudo-class#
Using :has()
1 we can remove that restriction:
:has(#show_completed:checked) .completed {
display: list-item;
}
That says that if our document contains a checkbox show_completed
which is checked, then all elements in the document with the
.completed
class should be visible (as list items). There's no
restrictions on where in the document the checkbox and .completed
elements are. Unfortunately, Firefox gives the following warning in its
developer tools:
This selector uses unconstrained :has(), which can be slow
The page I was testing on was small enough that I could not observe any such slowdown, so I'm not sure at what point that actually matters. But using a more precise selector gets rid of the warning:
.filters:has(#show_completed:checked) ~ .todo_list .completed {
display: list-item;
}
Limitations#
While it's fancy to be able to do this with just CSS, at some point sufficiently complicated logic is probably easier to express in JavaScript (e.g., if you want the full array of filters that Sleek has or want to support custom sort orders). You could handle things like filtering on context by dynamically generating CSS for each context actually in the todo.txt file, but at that point you should probably consider whether CSS is really the right tool for the job. Or you could go all in on CSS if you really want to.
<details>
/<summary>
#
As mentioned above, another way to accomplish a similar task is the
<details>
tag:
<details>
<summary>Click here for more info!</summary>
More info that's hidden until clicked.
</details>
It is only for hiding a single block, and the label to control the visibility has to be immediately next to the content to hide, so it can't be used to express the logic discussed in this post. But it does cover a very common pattern on web pages, especially in contexts like lists of frequently asked questions where the summary can make it clear which section the user is interested in reading.
It is also somewhat older than :has()
, with MDN saying it's been
widely supported since January 2020.
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.