Search topics
Multi-selects with silent ticks and mouse-only chips
A Skills field shows the chosen skills as chips whose × is an svg with a click handler, and ticks its list with a class, so a keyboard can't remove a chip and a screen reader never hears which skills are chosen.
<svg class="chip-remove" viewBox="0 0 16 16"> The problem
Keyboard users can't remove a chosen skill, and screen reader users can't tell which skills are chosen, so neither can check or correct the field.
A job board's profile form has a Skills field, drawn as the multi-select dropdown many component libraries ship. One rounded box holds the skills chosen so far, Design and Research, as chips, each with a small × to remove it, and a chevron at the end of the box opens a list of skills under it. With a mouse it all works: click the box to open the list, click a skill to choose it, click an × to drop a chip. Many libraries also let people type into the box to filter the list; that is left out here, and covered in autocomplete fields with silent, mouse-only suggestions. The field is commonly built in one of two ways.
In the first, a chosen skill leaves the list and shows only as a chip, as many multi-select components do by default. Tab goes past both chips straight to the chevron button, and Enter opens the list. The arrow keys move a highlight and Space chooses the highlighted skill, so skills can be added. But neither × ever takes focus, and the chosen skills are no longer in the list, so a keyboard user who chose Writing by mistake has no way to take it off the profile.
In the second, every skill stays in the list, and the chosen ones get a tick. A keyboard user can now open the list and untick Research there. A screen reader user hears "Skills, list box" and then each skill as the highlight moves, but nothing says that Design and Research are the ticked ones. A browser may even report whichever skill is highlighted as selected, as Chromium does, because the list doesn't say it takes several choices and no option says it is chosen. And pressing Space on Writing adds a tick and a chip on screen and says nothing at all. Tab still skips both ×.
Success Criterion 2.1.1 asks that everything a mouse can do can be done with a keyboard. Each × is an svg with a click handler. An svg is not a control: it can't take focus, so Tab never stops on it and no key presses it. In the first build, the × is the only way to remove a chosen skill, so a keyboard can add skills but never remove one. The second build gives the keyboard another way, unticking in the list, which is why it shows the other failure plainly.
Success Criterion 4.1.2 asks that a control's state, when the user can set it, is available to assistive technology. Whether a skill is chosen is such a state. In the second build, the tick is drawn by CSS from a class, and the options carry no aria-selected, while the list carries no aria-multiselectable. The browser builds what a screen reader reads from the markup, and a class means nothing to it, so it has no chosen state to pass on and no sign that the list takes several choices. W3C documents this as failure F15: a custom control that uses the accessibility API incompletely, here with roles but no state.
Multi-selects often hold what a profile, a filter or a post is about: skills, interests, tags, recipients. A wrong choice that can't be taken back, or choices that can't be checked, go out with the form.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users can't reach any ×. When chosen skills leave the list, a chip can't be removed at all, so a skill chosen by mistake stays on the profile.
- Without vision Screen reader users hear each skill's name and nothing about its tick, so they can't tell which skills are chosen, and a press of Space changes the field without a word.
To a generic scan, both builds look sound. The chevron is a named button, the listbox and its options have valid roles, and an svg with no role is a picture that needs no name, so nothing a rule knows of is wrong. A scan doesn't see that the picture takes clicks, or that a class draws a tick. EaseWeb's audit reports a remove cross inside a chip that Tab never reaches (ew:chip-remove-unreachable), and options in a listbox drawn as chosen by a class with no aria-selected (ew:option-selection-not-exposed); a listbox with two or more options marked aria-selected="true" but no aria-multiselectable is a lead for review. AI coding assistants asked for "a multi-select with removable chips" can write the × as an svg or span with an onClick, or add role="button" to it without tabindex or keys, and draw the ticks from a selected class. Only tabbing, pressing and listening shows whether it works.
Try it
Without the mouse: Tab to Skills, press Enter to open it, choose Writing with the arrow keys and Space, press Escape, then remove Design.
Skills is a dropdown that holds the chosen skills as chips. In Broken 1, Tab never stops on an ×, and a chosen skill leaves the list, so nothing you choose can be taken back. Clicking an × works. In Broken 2, untick in the list instead, and watch the panel: does it ever say which skills are ticked? On the fixed side, the checkbox fix moves between boxes with Tab.
Broken Can you fix this?
- Writing
- Coding
- Video editing
Screen reader says:
(Tab to Skills)
The fix
Give every chip a remove button Tab reaches, and let every option say in code whether it is chosen.
- 1Every chip's × is a button Tab reaches, named after its chip.
- 2Every option says in code whether it is chosen: a checkbox, a select or aria-selected.
- 3Removing a chip moves focus to the next chip or back to the field.
Chosen skills that only a mouse can remove
<span id="skills-label">Skills</span><div class="skills-field"> <span class="chips"> <span class="chip"> Design <svg class="chip-remove" data-for="skill-design" viewBox="0 0 16 16">…</svg> </span> <span class="chip"> Research <svg class="chip-remove" data-for="skill-research" viewBox="0 0 16 16">…</svg> </span> </span> <button type="button" class="skills-toggle" aria-labelledby="skills-label" aria-haspopup="listbox" aria-expanded="false" aria-controls="skills-list"> <span class="placeholder">Add a skill</span> <svg class="chevron" viewBox="0 0 16 16" aria-hidden="true">…</svg> </button></div><ul id="skills-list" class="skills" role="listbox" tabindex="-1" aria-labelledby="skills-label" hidden> <li id="skill-design" role="option" hidden>Design</li> <li id="skill-writing" role="option">Writing</li> <li id="skill-research" role="option" hidden>Research</li> <li id="skill-coding" role="option">Coding</li> <li id="skill-video" role="option">Video editing</li></ul>Checkboxes in the dropdown, with a button on each chip
<span id="skills-label">Skills</span><div class="skills-field"> <span class="chips"> <span class="chip"> Design <button type="button" class="chip-remove" data-for="skill-design" aria-label="Remove Design"> <svg viewBox="0 0 16 16" aria-hidden="true">…</svg> </button> </span> <span class="chip"> Research <button type="button" class="chip-remove" data-for="skill-research" aria-label="Remove Research"> <svg viewBox="0 0 16 16" aria-hidden="true">…</svg> </button> </span> </span> <button type="button" class="skills-toggle" aria-labelledby="skills-label" aria-expanded="false" aria-controls="skills-list"> <span class="placeholder">Add a skill</span> <svg class="chevron" viewBox="0 0 16 16" aria-hidden="true">…</svg> </button></div><div id="skills-list" class="skills" role="group" aria-labelledby="skills-label" hidden> <label class="skill"> Design <input type="checkbox" id="skill-design" name="skills" value="design" checked> </label> <label class="skill"> Writing <input type="checkbox" id="skill-writing" name="skills" value="writing"> </label> <label class="skill"> Research <input type="checkbox" id="skill-research" name="skills" value="research" checked> </label> <label class="skill"> Coding <input type="checkbox" id="skill-coding" name="skills" value="coding"> </label> <label class="skill"> Video editing <input type="checkbox" id="skill-video" name="skills" value="video"> </label></div>Why this fix: Use native HTML first
The chevron is a button, and the list works from the keyboard, so skills can be added. A chosen skill leaves the list, and the only way back is its ×, an svg that can't take focus: Tab skips it and no key removes the chip.
The dropdown keeps its look, and the list in it is now real checkboxes, so the browser makes each one a Tab stop, ticks it with Space, and tells a screen reader its name and whether it is checked: "Writing, checkbox, not checked", then "checked" after Space. The chevron button opens the list and moves focus to the first box; Escape closes it and returns focus to the button; moving focus or clicking away closes it too. The group takes its name from the "Skills" label. Each × is now a button holding the svg, so Tab reaches it and Enter or Space presses it, and its aria-label says which chip it removes: the button holds only a picture, so without it the button would have no name. When a chip goes, its button goes with it, so the script moves focus to the next chip's button, the one before when there is no next, or the chevron when none is left; without that, the focused button is gone and focus is lost to the page itself. Chosen skills stay in the list, ticked, so there is a second way to remove each one. The look changes a little: each row in the list shows an empty box until it is ticked.
All three pass 2.1.1 and 4.1.2, and all three need the same chip buttons. Use checkboxes: every browser and screen reader knows them, the browser gives each one its state, its keys and its touch behaviour with no script, and they keep the tick-list look. That is the reason to recommend them for nearly every multi-select. Keep the ARIA listbox when the list is long enough that one Tab stop for it matters, or a component already builds it, and accept that its keys and states are yours to test and maintain. Use the native select with multiple only for a short list, for people who already know its keys, such as on an admin screen, where an open list instead of a dropdown is fine, and write how to choose several beside it.
Every chip's button needs a focus ring of its own, and a target big enough to hit: the × drawn at 12 pixels can sit in a 24 by 24 pixel button. Name it after its chip, "Remove Design", not "Remove" or "Close", so a list of buttons read out of context still says which skill each one takes away.
When a chip is removed, focus has to go somewhere that still exists. The fixes move it to the next chip's button, then the one before, then the dropdown's chevron button, or the list of the native select. Moving focus doesn't say that Design was removed; a screen reader then reads the next button. If people need to hear that, it is a confirmation, covered in add to cart buttons that change a badge and say nothing.
Tag inputs, where people type a new tag and press Enter, often let Backspace in an empty field remove the last chip. That is a shortcut, not a replacement for the buttons: it can't remove a chip from the middle of the row. When the input suggests tags as people type, the suggestions follow autocomplete fields with silent, mouse-only suggestions. A row of chips cut short with a "+3" count needs that count to be a button that shows the hidden chips, or the hidden ones can't be reached at all.
Draw the ticks with borders or an SVG in currentColor, not with a background fill, so they stay in a Windows contrast theme, and give the highlighted option an outline as well as its background. Don't draw the tick as CSS text such as content: "✓" either: generated text is read as part of the option's name. Component libraries vary: some leave out aria-selected on unchosen options, some draw the × as a span, and some remove chosen options from the list. Check what they render, not their documentation. A dropdown that holds only one value is covered in custom dropdowns that keyboards and screen readers can't use, and a select-all checkbox for some of the options is the mixed-checkbox topic.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
Chosen values can look like anything, as long as the code says what is chosen.
Five more ways sites draw several choices. Click them, then put the mouse aside and try them with Tab, Space and Enter: three pass, one has an × that Tab never reaches, and one fills its pills without saying so in code.
In the design file
- Name the options in the design file as checkboxes, and each chip’s × as a button, so both are built on the real elements.
- Write each × button’s name beside it, with its chip’s word, such as “Remove Design”.
- Draw every × and every option with its focus ring, as it looks when reached with Tab.
- Mark where focus goes after a chip is removed, the next chip or, when none is left, the field.
- Keep each × visible at rest, not only on hover, since keyboard users never hover, and touch users see it only after a tap, if at all.
Passes
- Skill chips that gain a tick Every skill is a pill, and the pill is a checkbox. Off, a #6b7280 edge, 4.83:1; on, a teal edge, the #ccfbf1 tint and a tick before the word in #115e59, 6.73:1 on the tint. No separate chips to remove: Space unticks a pill where it stands.
- A tick list with a count and Clear all No chips: a line above the checkboxes says how many are chosen, beside a Clear all button. The count sits inside the group, where a screen reader finds it with the list, and every skill is unticked where it is listed, so there is nothing extra to reach.
- DesignWritingChips cut short by a “+2 more” button Two chips show, then a “+2 more” button that opens the rest and says it is open. Every chip, shown or opened, has its own × button, so none is out of reach.
Fails
- Design Writing Research CodingClean chips whose × shows on hoverWCAG 2.1.1 At rest the chips are plain words; pointing at one shows its ×. The × is not a button, so Tab never reaches it, and on a touch screen it shows only after a tap, if at all. Try it with Tab.
- Pills that fill when chosen, and say nothingWCAG 4.1.2 Each pill is a button, so Tab and Enter work, and a chosen one fills #0f766e with white words, 5.47:1. But the fill comes from a class: a screen reader hears “Writing, button” whether it is chosen or not.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Forced colors
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared multi-select component changes.
Limits
This is a starting pattern for a short list of fixed options. It doesn't cover grouped options, options loaded as the person scrolls, or reordering the chips. Test the complete journey, with your real options, in the browsers and assistive technology you support.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: keyboard (opens in a new tab)
- WCAG Understanding: name-role-value (opens in a new tab)
- WCAG Technique F15 (opens in a new tab)
- ARIA APG: listbox (opens in a new tab)
- w3.org: wai-aria-1.2 (opens in a new tab)
- w3.org: wai-aria-1.2 (opens in a new tab)
- developer.mozilla.org: select (opens in a new tab)
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.