Search topics
Search and filter results that update without a word
A live product search rewrites its result count as people type, but the line is in no live region, so a screen reader never says it.
count.textContent = countText(products.length, words); The problem
Screen reader users type a search or tick a filter and can't tell whether the results changed, how many there are, or that nothing matched.
A small kitchenware shop lists 6 products under a search field, with the line "Showing all 6 products" above them. The search runs as you type: a moment after you stop, the list redraws and the line changes to "3 products found for apron". A sighted user sees the list shrink and reads the number. A screen reader user types "apron", pauses, and hears nothing but the letters they typed. They don't know whether the search has run, whether it found three aprons or none, or whether they should press Enter. So they leave the field and walk through the list, item by item, to count what is there. When a search matches nothing, the line says so, and they don't hear that either.
"3 products found for apron" is a status message: it reports the result of something the person did, and it does not take focus. Success Criterion 4.1.3 asks that such messages can be presented to assistive technology through role or properties, so that they are announced without moving focus. The W3C's first example for the criterion is close to this one: search results that appear below the search button, with a line near their top that says how many were returned. Here the line is a plain p. Its text changes on screen, but nothing tells assistive technology to read the change, so the result of the search is visible and silent.
Live search is built for people watching the list. On a phone, the list is often below the on-screen keyboard, so even sighted users depend on the count line, and anyone who isn't looking at it at all gets nothing.
2 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Without vision Screen reader users hear their own typing, and nothing else. They can't tell whether the list changed, how many results there are, or that nothing matched, so they leave the field and move through the whole list to find out.
- Limited vision Screen magnifier users zoomed in on the search field don't see the list change below it, and those who use speech with their magnifier hear nothing about it either.
A scan loads the page and sees "Showing all 6 products" in the markup; it never types in the search field, so it never sees the line change, and nothing in the HTML says the line is a result. AI assistants asked to make search results accessible often put aria-live on the results list itself. That reads out every product name after each pause, or nothing at all when the framework replaces the list, because a region that arrives with its content is not announced. Others move focus to the results, which takes the caret out of the field in the middle of a word. Only a screen reader, used while searching, shows what is said.
Try it
Search for apron, stop, and listen.
The panel shows what a screen reader announces after each search. Is the number of products ever heard? Try a word nothing matches too.
Broken Can you fix this?
Showing all 6 products
- Linen apron
- Canvas apron
- Child's apron
- Tea towels, set of 3
- Oven gloves
- Bread tin
Screen reader says:
(Type in the search, then stop)
The fix
Write the count into a status region that was there before the search, after the results arrive.
- 1Render the status region with the page, outside the part that is rebuilt with the results.
- 2Announce the count once typing pauses and the results are in, including when nothing matches.
- 3Keep focus in the search field; never put aria-live on the results list.
<label for="q">Search products</label><input type="search" id="q"><p id="result-count">Showing all 6 products</p><ul id="results">…</ul>The visible count as a status region
<label for="q">Search products</label><input type="search" id="q"><p id="result-count" role="status">Showing all 6 products</p><ul id="results">…</ul>The words are right and they sit in the right place, but the p is not a live region, so the change is drawn and never announced.
The count line was already on the page, outside the list that is redrawn, so role="status" turns it into a polite live region the browser is watching from the start. After the pause, the screen reader finishes what it is saying, then reads "3 products found for apron". The caret stays in the field. What is heard is always the line that is shown, and the only new code is the attribute and the say helper. This works only while the count line itself survives each search: if the page rebuilds it along with the results, use the region below instead.
Both announce the number of results without moving focus. Use the visible count as the status region whenever that line survives each search: it is one attribute on an element that already exists, and what people hear can never drift from what they see. The hidden region is the right choice when the count is rebuilt with the results and you can't change that, for example in a third-party search widget or a results component that re-renders as a whole. It costs a second copy of the words to keep in step.
When to announce. The moment depends on how the search is started.
- A search that runs as people type: once typing has paused and the results are in, not on every keystroke. A pause of about half a second lets a fast typist hear one count instead of five.
- Filters that apply as soon as they are ticked: after the results for that change are in. The screen reader reads the checkbox's own state first, then the count.
- A filter panel with an Apply button: after Apply. Keep focus on the button, or return it to the button that opened the panel if the panel closes.
- A search that loads a new page: the count is not a status message there. Put it in the page title and the main heading ("3 results for apron"), which are read as the page loads.
Keep the region out of what is rebuilt. In a component framework, render the region in the layout or in a part of the search component that stays mounted, never inside a keyed block, an {#if} or a list that is replaced with the results. A region that is created along with its message is the same failure in different code.
Say the count, not the list. Never put aria-live on the results list. People hear every product name after each pause, or nothing when the list is replaced, and either way the one thing they asked for, how many, is lost. The count line is enough: they can move into the list when they are ready.
Zero results need words and a way out. "No products found" alone leaves people stuck. Say what to try, such as another word or fewer filters, and if the search corrects spelling ("Showing results for apron"), put that in the same message.
The same words twice. Adding a space to the search, or ticking a filter that changes nothing, writes the same words again, and some screen readers say nothing when a region's text does not change. The say helper clears the region first and writes after a short delay, so the count is announced every time. On the visible line, that clears the text for a tenth of a second, which is barely seen.
Slow searches. If results take more than a moment, write "Searching…" into the same region, then the count. Don't write it for searches that return at once; it only adds noise. See loading spinners that screen readers never announce for loading messages.
Politeness. role="status" waits for the screen reader to finish speaking, which is right for a count. Don't use role="alert" or aria-live="assertive": it would cut into the echo of each key people type.
Not fixes. Moving focus to the results or to the count line does get it read, but it takes the caret out of the field, and people have to find their way back to refine the search. aria-busy on the list does not announce anything. Suggestions that drop down under the field while typing are a different pattern, a combobox, with their own rules.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
4 checks, no mouse.
Test with: Screen reader, Keyboard
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the search component, the results template or the page layout changes.
Limits
Live regions behave differently across screen readers and browsers, and some drop a polite message when people keep typing. This pattern covers one search field on one page. Saved-draft messages are covered in status messages that are shown but never announced; infinite scroll and Load more are a loading topic of their own. Test your real search, with its real filters, sorting and pagination, with the screen readers your users use.
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.
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.