Filtering data

To make a form a landmark, use the role="form" and add an accessible name with aria-label="<name>".

Types of filters

There are two kinds of form filters:

  • Interactive: Updates when a user clicks a filter. Gets immediate feedback, but there is a performance hit because parts of the page must reload.
  • Batch: Users select several options before submitting the form, but it might yield zero results.

Client-side scripting

Server-side rendering requires a full page load, so users are aware of changes to the page. Client-side rendering changes the DOM, which provides only visual feedback.

You have two options to notify users about the filter results:

  • Move focus from the submit button to the region. Add tabindex="-1" to the HTML and use the focus() method
  • Output the results to a live region. The screen reader will announce changes to a live region.

Here is the HTML:

  • tabindex="-1" makes it focusable by JS
  • The <div> is labeled as a region and labeled by the heading
<div id="results" role="region" aria-labelledby="results_heading" tabindex="-1"> <h2 id="results_heading">Results</h2> <div role="status">Showing 40 of 40 records</div> <ol class="list"> <li><strong>Rubber Soul</strong><br>Beatles</li> <li><strong>Let It Be</strong><br>Beatles</li> <li><strong>Help</strong><br>Beatles</li> </ol> </div>

Here is the basic JS. There are two implementations of the finishQuery() function so you can either focus with JS or output the list to a live region:

const form = document.querySelector('form'); const results = document.querySelector('#results'); const list = document.querySelector('ol'); const liveRegion = document.querySelector('[role="status"]'); let records, filtered; // Option 1: Focus region with JS function finishQuery() { results.focus() } // Option 2: Output to live region function finishQuery() { const total = records.length; const found = filtered.length; liveRegion.textContent = `Showing ${found} or ${total} records`; } // Create a list of the results function showResults() { list.innerHTML = ""; for (let i = 0; i < filtered.length; i++) { const record = filtered[i]; const item = document.createElement('li'); const title = document.createElement('strong'); title.textContent = `${record.title} (${record.year})`; list.append(title, record.artist); list.append(item); } } // Filter list with user input function filterForm(e) { e.preventDefault(); const formData = new FormData(form); filtered = records.filter(record => { const artist = formData.get('artist'); const countries = formData.getAll('country'); const shipping = formData.getAll('shipping'); if (artist && record.artist !== artist) { return; } if (countries.length && !countries.includes(record.country)) { return; } if (shipping.length && !shipping.includes(record.shipping)) { return; } return true; }); showResults(); finishQuery(); } // Call a db. Example schema below async function getRecords() { // JSON response example [ { "artist": "Beatles", "title": "Let It Be", "year": 1969, "country": "GB", "format": ["LP", "CD"], "shipping": "eu" }, // ... ]; } // Fetch data add the event listener to the form getRecords().then(data => { records = data; filtered = data; form.addEventListener('submit', filterForm); });

Pagination

Pagination helps you manage lots of results data. Some tips:

  • wrap the pagination list in a <nav> element to create a landmark
  • use aria-current="page" on the current page
  • add a skip link a the beginning of the results region so users can access the pagination instead of cycling through the results first
<nav class="pagination" aria-labelledby="pagination_heading"> <h2 id="pagination_heading">Select Page</h2> <ol> <li><a href="#" aria-current="page">1</a></li> ... </ol> </nav>

Sorting results

Here is an example of a live region for the sorting results:

<fieldset id="sorting"> <legend>Sort by</legend> <div> <input type="radio" name="sorting" id="sorting_artist" checked="checked"> <label for="sorting_artist">Artist</label> <input type="radio" name="sorting" id="sorting_date"> <label for="sorting_date">Date</label> </div> </fieldset> <div id="live-region-sorting" hidden="hidden">Sorted by [type]</div>