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 thefocus()
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>