HTML Button Group for the OCD
The CSS :has pseudo-class enables squeaky clean markup for group components, like a button group.
Mdash recently shipped its button group using :has and the OCD within me ranks the markup 5 out of 5:
<div role="group">
<button ord="secondary" aria-pressed="true">One</button>
<button ord="secondary">Two</button>
<button ord="secondary">Three</button>
</div>
With Firefox recently adding :has support, this now works across all browsers.
Previously, you would need to define a custom tag (or class if you're not following TAC CSS) for the grouping element. That's no longer necessary because :has effectively selects a parent based on its children, like button children in this case. A div with the group role wasn't enough of a selector in the past to define a button group, but now we can look into its content, which is what :has makes possible, and so with that the cleanest CSS rule for a button group is now:
[role="group"]:has(> button) {
display: inline-flex;
& button {
/* Button default styles */
&[aria-pressed="true"] {
/* Pressed button styles */
}
&:first-of-type {
/* First button styles */
}
&:last-of-type {
/* Last button styles */
}
}
}
That's native CSS nesting by the way, not SCSS.
I have learned that it is best to let the consuming app manage its own clicks for simple components like this, so this button group is CSS-only. An app can easily toggle its buttons as desired using event delegation and the ariaPressed
property:
<div onclick="toggle(event)" role="group">
<button ord="secondary" value="ready">Ready</button>
<button ord="secondary" value="complete">Complete</button>
<button ord="secondary" value="overdue">Overdue</button>
</div>
<script>
// Allows only one button pressed at a time
function toggle(e) {
e.currentTarget.querySelectorAll('button').forEach(btn => {
btn.ariaPressed = btn === e.target ? 'true' : 'false';
console.log(`Button: ${btn.value}, Pressed: ${btn.ariaPressed}`);
})
}
</script>
Using whatever techniques the app has for handling events, it can implement radio-style logic or checkbox-style logic or maybe a button group with link buttons. This approach gets a nice button group style and while letting the app leverage native API for all sorts of interactive use cases.
It's gonna be fun looking at components from the perspective of :has. I can imagine several more instances where this will mean cleaner, more standards-based markup.