We can :has a better accessible checkbox
Charlie Triplett
Accessible design systems expert & UI engineer, Author of The Book on Accessibility, Inventor, Workshop leader, Corporate trainer
I've been making the same checkbox and radio for over 10 years. It's accessible to every keyboard and desktop screen reader, uses CSS for custom styling and is 100% reliable in every platform.
But then I read Sara Soueidan's great overhaul of checkboxes and saw what I missed. My pattern leaves out a really great feature available to touch device screen readers.
What I've been doing
The markup:
<input type="checkbox" id="alpha">
<label for="alpha">Alpha</label>
The CSS:
First I encode my checkmark SVG itself as a URI CSS variable. This is efficient and can load once with the CSS, no additional SVG needs to be loaded.
:root {
--icon-checkbox: url("data:image/svg+xml,%0A%3Csvg etc etc etc);
}
Hiding a checkbox using the clip property is a very common way to visually hide an element while leaving it available to the accessibility tree.
// Visually hide the checkbox, but keep it available in the DOM
input[type="checkbox"] {
position: absolute;
left: 0;
width: 1px;
height: 1px;
clip: rect(1px, 1px, 1px, 1px);
}
Next, I create a styled checkbox with a :before pseudo element using the next sibling combinator.
// This creates a styleable pseudo element in place of the hidden input
input[type="checkbox"] + label:before {
content: '';
display: block;
position: relative;
top: 0rem;
left: 0rem;
width: 1rem;
height: 1rem;
border: 1px solid #777;
background-color: #fff;
background-repeat: no-repeat;
background-position: center center;
background-size: 0% 0%;
background-image: var(--icon-check-white);
}
// When checked, fill the pseudo checkbox with a checkmark
input[type="checkbox"]:checked + label:before {
background-color: #222;
background-size: 100% 100%;
}
(This is a bare bones example. In real production code, I arrange the label text and pseudo element with CSS grid layout to perfect its layout, but you get the idea.)
What's wrong
My current technique for coding checkboxes is adequate for desktop keyboard users, people using screen readers, and swiping gestures on touch screens, but doesn't make full advantage of gestures available to people using screen readers on touch screens.
There's more to touch device screen readers than swiping
Touch gestures for screen readers include the ability to drag a finger across the screen to explore/hear the content of the page as the finger moves. So if you drag a finger over the checkbox, it should read the name, role and state.
This technique visually hides/minimizes the checkbox input itself, meaning that there's no surface area in which to discover the input itself with this gesture.
Why it's a problem
Dragging gestures over a checkbox label does not (predictably) read the role and state of the checkbox. People using a touch device screen reader will have to individually swipe every component to read each checkbox input. And that's a drag if you're familiar with the drag gesture. See what I did there?
How to fix it
Sara Soueidan's post solves for this using an svg and additional spans and the adjacent siblings selector. It's an admirably elegant solution, and the inspiration for what follows.
领英推荐
Taking it one step further
I like to keep my markup ascetically clean and save the complexity for CSS. I'm fanatical about minimal markup, reducing JavaScript, and bending the hillbilly dark arts of CSS to my will.
Sadly, most people with the job title "front end developer" are really just JavaScript engineers with little understanding of markup, so it's best to require as little as possible. Meanwhile their disdain for CSS will keep it safe from prying overrides.
The :has pseudo-selector the rescue!
By using a combination of the :has pseudo-selector and @supports, it's now possible to create a clean checkbox that fulfills all our acceptance criteria for drag gestures.?
This technique is performant, supports older browsers, and uses no JavaScript while not adding any additional placeholder elements to our markup.
Here's a working example using this article's markup and an overly elaborate fancy working example.
The markup
Firstly, We place the input inside the label, which means the adjacent sibling selector can't be applied to the label now, but the new :has selector will work for us.
<label for="alpha">
<input type="checkbox" id="alpha">
Alpha
</label>
The CSS / Sass
We begin by styling the default checkbox experience as much as possible. If someone is running an older browser that doesn't support :has, that's fine.
As Heydon Pickering has pointed out, the default browser experience is not a bad experience. Again: the default browser experience is not a bad experience.
// Style the default checkbox as much as possible
input[type="checkbox"] {
width: 1rem;
height: 1rem;
accent-color: #222
}
Now, we use @supports to add some conditional styles to hide the input if :has is supported.
// If this browser supports :has, hide checkbox input
@supports selector(:has(*)) {
input[type="checkbox"] {
opacity: 0;
position: absolute;
z-index: 1; // Specify z-index so that cursor remains pointer on hover
}
}
For the label, we're still changing the styled pseudo checkbox based on the state of the checkbox, but using the :has property instead of the adjacent sibling selector.
label {
position: relative; // Contains the input
display: block;
&:has(input[type=checkbox]) {
// Draw the empty pseudo checkbox
&:before {
content: "";
display: inline-block;
width: 1rem;
height: 1rem;
border: 1px solid #777;
background-image: var(--icon-checkbox);
background-repeat: no-repeat;
background-position: center center;
background-size: 1% 1%;
}
}
// When checked, fill the pseudo checkbox with the checkmark
&:has(input[type=checkbox]:checked) {
&:before {
background-color: #222;
background-size: 80% 80%;
}
}
}
Conclusion
Supporting accessibility always leads to new innovation.
By supporting this less known screen reader gesture, we can have a more performant checkbox (and radio input) that maintains backwards compatibility with older platforms.
Do good with better content /The Content Strategy Toolkit/ Not looking for marketing or sales services
7 个月One of my favorite things about this post is how you model doing better when we know better.
Digital Accessibility Professional #DisabilityJustice
7 个月Can has, haha :)
Building a more inclusive web
7 个月Even better: marketing types could just be ok with browser-native styling constraints and things not exactly matching across browsers. The majority of users aren't jumping between browsers and comparing UIs. #PixelPerfectParity is a waste of money. I shudder to think how much code has been and continues to be written to overcome this "problem." Even if the problem shouldn't exist, this is a slick solution. Has :has support improved since I checked last? #FunctionalityFirst #a11y
Accessible design systems expert & UI engineer, Author of The Book on Accessibility, Inventor, Workshop leader, Corporate trainer
7 个月Think this is a good checkbox pattern? Share it! Spread the word.