Practical accessibility walkthrough
What to know
Accessibility needs to hold up after every state change. Test keyboard focus, dialog behaviour, status announcements, form errors and client-side navigation through complete journeys. Combine automated checks with manual and assistive-technology testing, and check the relevant WCAG requirement rather than relying on a single scan.
A page can pass a basic accessibility scan and still become difficult or impossible to use the moment someone opens a menu, changes a product option or encounters a form error. The initial document may have headings, labels and sufficient contrast. The problems begin when JavaScript changes what is on the screen without preserving focus, reading order or meaningful feedback.
This walkthrough examines a practical route through a modern web application: navigating by keyboard, opening a modal, adding an item to a basket, receiving an error and moving to another page without a full browser reload. It applies the principles of WCAG 2.2 and the WAI-ARIA Authoring Practices Guide. Automated testing is part of the method, but it is not the method on its own.
Start with the journey, not the tool
Consider a customer who wants to choose a product, examine delivery information and complete an order. A screen reader user needs to know which control is currently selected and whether the price changed. A keyboard-only user needs to reach and leave a delivery dialog. A user navigating with magnification needs the focus indicator to remain visible when a sticky header covers part of the page.
Run the journey before reading a list of automated findings. Can the visitor arrive at the same outcome without a mouse? Does the application tell them what changed? Is a temporary overlay genuinely temporary, or has the user become trapped in it?
Record the browser, operating system, viewport, input method, assistive technology where relevant and the steps needed to reproduce each problem. A single automated scan cannot establish the accessibility of a complex interaction.
1. Keyboard navigation: follow the real focus order
Load the page, then navigate using Tab and Shift+Tab. Activate buttons with Enter or Space as appropriate. Use arrow keys where the particular component pattern calls for them, such as radio groups or tabs. Use Escape to test dismissible overlays. Do not assume every custom control should respond to every key in the same way.
Check that the focus order follows the visual and logical reading sequence. Pay special attention to mobile menus, sticky navigation, search suggestions, off-canvas baskets and cookie controls. A button that looks first on screen but is reached after the footer can be confusing even when it is technically focusable.
Visible focus is essential. Removing the browser outline without an equivalent replacement makes keyboard navigation difficult. A simple pattern is:
:where(a, button, input, select, textarea):focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
This is a starting pattern, not a guarantee of sufficient contrast against every background. Test it on photographs, gradients, dark surfaces and high-contrast settings. WCAG 2.2 also includes Focus Not Obscured (Minimum) at Level AA: a sticky toolbar or cookie banner must not entirely hide the focused control. The more demanding Focus Appearance criterion is Level AAA, not a general Level AA requirement.
Provide a mechanism to bypass repeated navigation. A visible-on-focus Skip to main content link is often the simplest solution:
<a class="skip-link" href="#main-content">Skip to main content</a>
<main id="main-content"><h1>Products</h1></main>
The link must work with the actual page structure, not merely exist in source code. Where a sticky header can obscure a target, apply suitable scroll offset styling and check the result. WCAG requires a bypass mechanism; a skip link is a common technique, not the only valid implementation.
2. Modal dialogs: use native behaviour where possible
An accessible modal is more than a panel with a dark backdrop. It interrupts the current task and temporarily contains keyboard interaction. On opening, focus should move to an appropriate element inside the dialog. Tab and Shift+Tab should stay within the modal sequence. Escape should normally close it, and focus should return to the invoking control unless the workflow makes another target more logical.
The platform already provides much of this behaviour through the HTML <dialog> element and its showModal() method. A simplified example is:
<button id="delivery-open" type="button">Delivery information</button>
<dialog id="delivery-dialog" aria-labelledby="delivery-title">
<h2 id="delivery-title" tabindex="-1">Delivery information</h2>
<p>Choose a delivery method at checkout.</p>
<button id="delivery-close" type="button">Close</button>
</dialog>
<script>
const dialog = document.getElementById('delivery-dialog');
const openButton = document.getElementById('delivery-open');
const closeButton = document.getElementById('delivery-close');
openButton.addEventListener('click', () => dialog.showModal());
closeButton.addEventListener('click', () => dialog.close());
</script>
Opening this with showModal(), rather than merely adding an open attribute, is important: the browser handles modal behaviour, including making background content inert. Test initial focus as part of the real design. For a long explanation, focusing a heading with tabindex="-1" may make more sense than immediately sending someone to a confirmation button at the bottom. A destructive confirmation may need initial focus on the safer option.
For custom dialogs, do not assume a React useRef and a hand-written Tab handler constitute an accessible focus trap. The implementation must handle focusable elements changing, nested overlays, disabled controls, background inertness, opening and closing, and restoration of focus. A well-tested library or native dialog is often safer. Never mark a panel aria-modal="true" unless it genuinely behaves as a modal for all users. W3C's modal dialog pattern explains the interactions, while Technique H102 covers the native element.
A focused test: Open the dialog using the keyboard; traverse every control in both directions; press Escape; check that focus returns somewhere sensible. Then repeat with a screen reader and on a small screen. If the opener is removed as part of a successful action, move focus to a logical remaining element rather than a vanished button.
3. Status messages: announce changes without interrupting everything
A basket icon changes from zero to one. The customer sees the number update, but a screen reader user may hear nothing. Adding an item is not automatically communicated just because the DOM changed.
Use a persistent status region for non-urgent updates:
<p id="basket-status" role="status" aria-live="polite"></p>
When an item is added successfully, update its text to a concise message such as “Item added to basket. Basket contains 1 item.” The region should exist before the update where possible, and the announcement should not be duplicated by several competing regions. role="status" already implies polite live-region behaviour; the explicit aria-live in the example makes the intended behaviour easier to recognise, though it is redundant.
Not every dynamic change needs a live announcement. Opening an accordion should expose its expanded state through the button's aria-expanded and an appropriate relationship to its panel. Selected filters should be represented by their controls' actual states. Live regions are best reserved for changes that users need to hear but that do not otherwise move focus.
Use assertive interruptions sparingly. role="alert" or aria-live="assertive" can be appropriate for genuinely urgent problems, but an assertive message for every validation keystroke creates noise and can interrupt someone reading the form. WCAG 2.2 Success Criterion 4.1.3 concerns status messages that do not receive focus; it does not require every message to be assertive.
4. Forms: make errors discoverable and correctable
Consider a checkout where the postcode is missing. Colouring the field border red is not sufficient. The error needs understandable text and a programmatic relationship to the affected input.
<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" aria-invalid="true"
aria-describedby="postcode-error" autocomplete="postal-code">
<p id="postcode-error">Enter a postcode to see delivery options.</p>
Set aria-invalid="true" only while the field is invalid. Remove it or change its value when the problem is corrected. Ensure the field remains keyboard accessible and that the form does not discard previously entered information.
For a multi-field submission with several errors, a linked error summary near the top of the form can help. Move focus to the summary when appropriate, or to the first invalid field, and test that users can navigate to each problem. Do not combine a focus-moving summary with several simultaneous assertive announcements. Explain how to fix the error, not merely that something went wrong.
Also test network failure. If a payment or submission request times out, the interface must distinguish between “still processing”, “not submitted” and “confirmed” states. Announcing success before the server confirms it can mislead everybody, including assistive-technology users.
5. Client-side routing: change the page without losing the visitor
Single-page applications can replace an entire view while leaving keyboard focus on a navigation control that no longer describes the current content. A visual heading changes, but a screen reader may not announce that a new route is active.
For a genuine route transition, update the document title and move focus to a sensible target, commonly the page's main heading or main region, when that helps establish context. A heading may receive tabindex="-1" to allow programmatic focus without adding it to the ordinary tab order. Avoid forcing focus on every small state update, filter change or query-string adjustment. Back and forward navigation, preserved form values and scroll restoration also deserve testing.
A robust routing test answers three questions: Where am I? What changed? Where does the next Tab go? If any answer is unclear, the application needs more than a passing static audit.
6. Moving panels, carousels and drag interfaces
Creative applications often use carousels, horizontal scrolling, sortable cards and drag-and-drop interfaces. They are not inherently inaccessible. Problems occur when the only way to perform an action is a complex pointer gesture or when animation hides the current focus.
Provide keyboard-operable controls where appropriate, label the outcome of an action and test the reduced-motion preference. For interfaces that rely on dragging, WCAG 2.2 includes Dragging Movements at Level AA, with exceptions where dragging is essential or determined by the user agent. Also consider Target Size (Minimum): the Level AA criterion is generally 24 by 24 CSS pixels, subject to specified exceptions and spacing alternatives. A generous target is often better than designing to the narrowest acceptable interpretation.
A carousel's next and previous controls should communicate what they do. Automatic movement should be controllable when the relevant WCAG conditions apply. Avoid shifting content beneath a user who is reading, typing or trying to activate a control.
A repeatable testing sequence
Use the same route through the application each time, both before and after fixes:
- Load the page, enter by keyboard and activate the skip mechanism.
- Tab through navigation, product options, forms and the primary action; check order and focus visibility.
- Open a dialog, traverse its controls, close it with
Escapeand inspect the return focus. - Select a product option; verify its state, price and availability announcements.
- Submit an incomplete form; find the error, correct it and submit again.
- Trigger a slow or failed request; verify the pending, success and failure states.
- Navigate to another route and use browser Back; check heading context, focus and preserved state.
- Repeat important steps with a screen reader and on a narrow viewport; use automated checks to catch additional issues.
For broader coverage, combine browser accessibility-tree inspection, automated rules such as axe-core, keyboard testing and representative screen-reader/browser combinations. NVDA with Firefox or Chrome, and VoiceOver with Safari, offer useful coverage, but no single combination stands in for every user or device.
What a review record should say
A useful finding describes the task, observed behaviour, impact and retest condition. For example:
Task: Open delivery information from the product page. Observed: The modal appears, but focus remains on an obscured background button. Impact: Keyboard users cannot reliably locate the new content. Remedy: Open a true modal dialog and assign sensible initial focus. Retest: Open, navigate and close with keyboard and screen reader, including on mobile.
This is stronger than a vague instruction to “fix ARIA” or a screenshot of an automated scan. It tells a developer what to reproduce and tells a reviewer what must work afterwards.
The Web Dev Awards perspective
The Web Dev Awards judging approach treats accessible interaction as part of development quality, alongside performance, technical execution and creative development. A sophisticated interface should not require users to surrender basic control of focus, navigation or feedback. The most impressive applications make those foundations feel natural, even when the visual design is unconventional.
Accessibility is not something that happens only at page load. It must survive every state change the application asks a visitor to understand.
Frequently asked questions
Is passing an automated accessibility scan enough?
No. Automation can identify many detectable issues, but it cannot reliably evaluate every keyboard flow, focus transition, instruction, screen-reader announcement or task outcome. Manual and assistive-technology testing remain necessary.
Must every modal use a manually coded focus trap?
No. For many interfaces, the native HTML dialog element opened with showModal() provides the modal focus and background behaviour. Custom implementations need equivalent behaviour and thorough testing.
Should every form error use aria-live="assertive"?
No. Make errors discoverable through labels, descriptions and clear focus management. Reserve assertive announcements for situations that warrant an immediate interruption.
Is a skip link compulsory?
WCAG requires a mechanism to bypass repeated blocks of content. A functioning skip link is a straightforward, widely used solution, but it is not the only possible method.
Further reading: WCAG 2.2 · Modal Dialog Pattern · Status Messages · Web Dev Awards judging
