Lesson 11 · Part 2 · Filters

Filtering data

A filter is just a small form that changes a list. Let people set several options first, then press Apply. When the list changes, say so in words, because a screen reader user cannot see the list jump.

In one minute

In plain words

Think of ordering at a counter. You say "small, oat milk, no sugar", then the barista starts. If the barista began making a drink at the word "small" and restarted at every change, you would be talking over the noise. A filter that updates on every keystroke does that to a screen reader.

1. Pick the right native control

Browsers already ship controls that work with the keyboard, touch and screen readers. Choose by asking two questions: how many options are there, and can the user pick more than one? Related checkboxes or radios belong in a <fieldset> with a <legend>, so the question is read along with the answers.

Lab: which control fits?

Step 1. Choose the situation. The matching control appears. Try it with the keyboard.


          

Avoid custom dropdowns made of divs unless you rebuild everything the native select gives you. A multi-select <select multiple> needs Ctrl or Shift to choose several items, which many people do not know, so checkboxes are friendlier for a short list.

2. Apply in a batch, not on every change

On a closed select, pressing ↓ can change the value (and fire a change event) in some browsers. If the list reloads on each change, a keyboard user who wants "Coast" passes through "North" first and triggers extra updates. WCAG 3.2.2 (On Input) says changing a setting must not cause a surprise change of context unless the user was told first. An Apply button avoids all of this.

Lab: count the interruptions

Step 2. Click the region select, then press ↓ a few times. Compare both versions.

Announcements the screen reader would make: 0

    3. Say what happened

    After the list changes, sighted people see it. A screen reader user hears silence. There are two good fixes. A role="status" region is a polite announcement: it waits until the user stops speaking and does not move focus. Moving focus to the results heading (give it tabindex="-1") is the better choice when the list is the very next thing the user wants to read. Do not do both, or the user hears it twice.

    Lab: filter and listen

    Step 3. Choose how results are announced, pick a region, press Apply. Try "None" as the region too: it gives zero results.

    Results

      Press Apply to hear the result.

      Always handle the empty case with words ("No routes match. Try another region.") and a way to clear the filters. An empty list with no text looks like a broken page.

      4. Pagination and the URL

      Page numbers are links or buttons, so they need names that make sense out of context. A button that says only "2" is heard as "2, button". Add aria-current="page" to the current one. Put the filters and the page in the URL (?region=north&page=2), so the Back button, a refresh and a shared link all work.

      Lab: name the page buttons

      Step 4. Switch the naming, then press a page button.

        Focus a page button.

        Common mistakes

        You seeWhy it is a problemFix
        List reloads on every changeKeyboard users trigger many updates; a screen reader repeats itself.Apply button, or debounce and announce once.
        No message after filteringScreen reader users do not know anything changed.role="status" or focus the results heading.
        Both status and focus moveThe result is announced twice.Choose one.
        Empty list, no textLooks like an error or a loading failure."No routes match" + a Clear filters button.
        Checkbox group with no legendOptions are heard without the question.fieldset + legend.
        Page buttons named "1, 2, 3"No context in a list of buttons."Page 2" + aria-current="page".
        State only in JavaScriptBack, refresh and sharing lose the filters.Keep it in the query string.
        Try it yourself
        1. In lab 2, use only the keyboard: Tab to the select, press ↓ twice. How many announcements did each version make?
        2. In lab 3, choose "None (no matches)" and apply. Is the empty result explained?
        3. Sort controls belong on table columns. The next lesson does that.

        Remember

        Edit this example in the playground