- Learn
- Computer Science & IT
- Programming
- Web Development Basics
- Building Accessible Web Pages
Building Accessible Web Pages
BeginnerLesson 7 of 8 in Web Development Basics Course
What you will learn
- Use HTML elements for their meaning so that browsers, screen readers and keyboards can work with your pages, and understand what assistive technologies receive from your code.
- Explain how assistive technologies read a web pageDescribe who depends on accessible code, how the browser turns HTML into an accessibility tree, and how WCAG 2.2 level AA sets a testable target.
- Give every page a clear, navigable structureSet the page language and title, mark up landmarks, headings and lists, and keep the source order meaningful.
- Choose native elements for links, buttons, controls and tablesPick links for navigation and buttons for actions, write link text that makes sense, prefer built-in controls, and mark up data tables so their headers are announced.
- Explain how assistive technologies read a web page
- Give images, icons, graphics, audio and video a text equivalent that serves the same purpose, and hide purely decorative content from assistive technologies.
- Write alt text that fits each image's purposeDecide whether an image is informative, decorative, functional, text or complex, and write the matching
alttext. - Name icons and SVG graphics and give media text alternativesGive icon buttons and inline SVG an accessible name, hide decorative graphics, and add captions, transcripts and audio description to audio and video.
- Write alt text that fits each image's purpose
- Let people reach and use every control with the keyboard in a logical order, always see where focus is, skip repeated content and keep their place when the page changes.
- Operate a page by keyboard in a logical focus orderUse the standard keys, know which elements receive focus, use
tabindexcorrectly and keep the focus order logical, free of traps and invisible stops. - Show focus clearly and make controls easy to hitKeep a visible, high-contrast focus indicator, keep the focused element from being covered, and give pointer targets enough size or spacing.
- Add skip links and manage focus when content changesLet keyboard users skip repeated blocks, move focus sensibly when content opens, closes or disappears, and avoid changes the user did not ask for.
- Operate a page by keyboard in a logical focus order
- Choose color combinations with enough contrast, never let color carry meaning on its own, and keep pages usable when people zoom in or enlarge text.
- Measure and meet color contrast requirementsCalculate and check contrast ratios for text and for interface parts, and apply the WCAG AA thresholds and their exceptions.
- Use color together with other cuesMake sure links, errors, required fields, charts and status indicators do not depend on color alone.
- Keep pages usable when zoomed or when text is enlargedUse relative units and flexible layouts so that pages survive 200% text size, 400% zoom and user text spacing without losing content.
- Measure and meet color contrast requirements
- Label every control, group related ones, give instructions and autocomplete information, and report errors and status messages so that everyone can complete a form.
- Label and group every form controlConnect visible labels to inputs, group radio buttons and checkboxes, name buttons, and choose input types that match the data.
- Give instructions, mark required fields and support autofillConnect hints to fields, show which fields are required, add autocomplete tokens, and avoid steps that make people retype or memorize information.
- Report errors and status messages clearlyIdentify and explain input errors in text, link them to their fields, and announce messages that appear without moving focus.
- Label and group every form control
- Add ARIA only where HTML has no native answer, keep its states in sync, and check pages with automated tools and by hand with a keyboard, zoom and a screen reader.
- Apply the rules for using ARIAExplain what ARIA changes and what it does not, prefer native HTML, avoid harmful ARIA, and know how an element's accessible name is chosen.
- Communicate changing states with ARIAUse
aria-expanded,aria-current,aria-pressedandaria-hiddencorrectly and keep every state in sync with what the page shows. - Run automated accessibility checks and interpret the resultsUse automated checkers to find machine-detectable problems, understand what they cannot judge, and turn their reports into prioritized fixes.
- Test by hand with a keyboard, zoom and a screen readerWalk through a page with the keyboard alone, check zoom and reflow, try the basics of a screen reader, inspect the accessibility tree and record what you find.
- Apply the rules for using ARIA
Building Accessible Web Pages study guide
1Structure pages with semantic HTML
A page’s HTML gives browsers and assistive technologies the information they need to explain and operate it. Start by understanding the accessibility tree, then build a page outline that people can navigate through landmarks, headings and lists. Choose links, buttons and table elements for their purpose, and let CSS handle their appearance. These choices give every later feature a reliable structure.
1.1 Explain how assistive technologies read a web page
An accessible page lets people get information and complete tasks with the tools they use. Those tools may include a keyboard, a screen reader, a magnifier or voice control. A page can look finished while its code still blocks these users.
What assistive technology receives
The browser reads HTML into the DOM, its model of the document. It derives an accessibility tree that describes meaningful objects to assistive technologies. A control needs a role, a name and any relevant states or properties. For example, a checkbox can have role "checkbox", name "Email offers" and state "checked".
A screen reader speaks content or sends it to a braille display. NVDA and JAWS run on Windows; Narrator is built into Windows, VoiceOver into macOS and iOS, and TalkBack into Android. Screen readers can navigate headings, links and controls without reading every line. Some can also report font or color settings. Magnifiers show the enlarged visual page, so visible clarity matters too.
Voice control lets a person say a control's name, such as "click Search". Switch devices can send keyboard-like commands. Keyboard support and accurate names therefore help several groups of users, not just people listening to speech.
Start with the right element
Compare these two ways to make a Save control:
<button type="button">Save</button>
<div class="btn" onclick="save()">Save</div>
The button already exposes its role and name. It receives Tab focus and responds to Enter and Space. CSS can make the second line look identical, but a div has none of that button behavior. A screen reader may trigger its click handler in reading mode, yet it does not announce the element as a button. A person navigating with Tab cannot reach it.
Semantic HTML means choosing an element for what the content or control means. Use CSS to control its appearance. This gives the browser a sound starting point and avoids having to rebuild native behavior with script.
Know the target
The W3C publishes the Web Content Accessibility Guidelines, or WCAG. Its success criteria are testable requirements, each identified by a number, a name and a level. For example, 1.4.3 Contrast (Minimum) is level AA. WCAG 2.2 became a Recommendation in October 2023 and was updated in December 2024.
Level AA conformance requires every level A and level AA criterion. Passing a few AA checks is not enough. Level AAA adds further requirements. WCAG 2.2 adds nine criteria to 2.1, six at A or AA, and removes the obsolete 4.1.1 Parsing criterion.
Accessibility also includes readable enlarged text, captions for deaf and hard-of-hearing people, clear instructions and helpful errors. Building these features into the page from the beginning avoids having to replace unsuitable HTML after the layout is complete.
Key Takeaways
- Choose HTML elements for their meaning, then style them with CSS.
- The accessibility tree exposes roles, names, states and properties.
- Native buttons provide keyboard behavior that a styled
divlacks. - WCAG 2.2 level AA requires all level A and AA success criteria.
1.2 Give every page a clear, navigable structure
A useful page structure gives people several ways to find their place. A descriptive title identifies the page, landmarks identify its regions, and headings reveal the outline. These features need real HTML meaning, even when the visual design already looks clear.
Identify the page and its language
Set the main language on the root element, for example <html lang="en-US">. Screen readers use it to choose pronunciation rules. Mark a change within the page on the relevant element: <span lang="fr">bon appétit</span>. These support 3.1.1 Language of Page (A) and 3.1.2 Language of Parts (AA).
Give each page a unique, descriptive <title>. "Order a birthday cake – Green Leaf Bakery" is more useful in tabs, bookmarks and screen reader announcements than "Green Leaf Bakery – Page 4". Put the specific topic first so users can distinguish similar tabs quickly.
Divide the page into regions
Use <main> for the primary content, <nav> for navigation and <aside> for supporting content. A page-level <header> supplies the banner landmark and a page-level <footer> supplies content information. Header and footer elements nested inside sectioning regions do not become those page-level landmarks.
Use one visible <main>. If there are two navigation regions, distinguish them with names such as <nav aria-label="Main"> and <nav aria-label="Breadcrumb">. The role already says "navigation", so the label can stay short. A <section> or <form> becomes a landmark only when it has an accessible name.
Build an outline before choosing font sizes
For the bakery order page, a clear outline could be:
<h1>Order a birthday cake</h1>
<h2>Choose a size</h2>
<h2>Choose a flavor</h2>
<h3>Chocolate</h3>
<h3>Lemon</h3>
<h2>Pickup or delivery</h2>
Use one main-topic h1, main sections at h2 and their parts at h3. Avoid skipping levels on the way down. Choose heading levels for structure; adjust their size with CSS. A bold paragraph does not enter the screen reader's heading list. Conversely, wrapping an ordinary introduction in h2 adds a section that the content does not have.
Preserve grouping and reading order
Use <ul> for unordered items, <ol> for ordered steps and <dl> for term–description pairs. Screen readers can announce a list's size, helping users judge how much follows. Navigation links can sit in a ul inside nav. In Safari, removing bullets with list-style: none may suppress list announcements in VoiceOver; role="list" on that list restores them.
Write the HTML in a meaningful reading order. Flex order, reversed flex direction, grid placement and absolute positioning change what people see without changing the DOM order. A row that visually starts with "Sign in" but reaches it last with Tab is harder to follow. Fix the source order before arranging the layout.
Work through a page shell
A bakery site has a main menu and a second set of breadcrumb links. Give the regions distinct names while keeping both sets of links available:
<nav aria-label="Main">
<ul><li><a href="/bread">Bread</a></li></ul>
</nav>
<nav aria-label="Breadcrumb">
<ol><li><a href="/">Home</a></li></ol>
</nav>
<main>
<h1>Order a loaf</h1>
<h2>Choose your bread</h2>
</main>
Inspect the landmark list: Main and Breadcrumb identify different navigation purposes, followed by the primary content. Then inspect the heading list: it should show the page topic and its first section. These two lists offer different routes through the same document; neither depends on the menu's colors or the heading's font size.
Key Takeaways
- Set the page language and mark passages in another language.
- Give each page a specific title and one visible main region.
- Use real heading levels to express the outline.
- Use list elements for groups of items and ordered steps.
- Keep visual order and meaningful source order aligned.
1.3 Choose native elements for links, buttons, controls and tables
A control's element tells users what to expect. A link promises a destination; a button promises an action. Native controls and table elements also supply relationships that would be difficult to recreate from styled generic boxes.
Choose navigation or action
| Purpose | HTML | Standard activation |
|---|---|---|
| Go to another page, a file or a page location | <a href="/classes">Baking classes</a> | Enter |
| Perform an action on the current page | <button type="button">Show filters</button> | Enter or Space |
An anchor without href is not a normal keyboard-focusable link. An anchor with href="#" and a menu-opening click handler announces navigation even though it performs an action. Use a button for that menu. Inside a form, give non-submitting buttons type="button"; otherwise their default behavior can submit the form.
Make destinations clear
A screen reader can collect the page's links into a list. Repeated "Read more" links are difficult to distinguish there. "Read about sourdough classes" names the destination directly. WCAG 2.4.4 Link Purpose (In Context) (A) allows nearby context to explain a link, but meaningful link text also works when heard on its own.
Tell users about a download or new tab in the link text: "Price list (PDF, 2 MB)" or "Booking form (opens in a new tab)". A new tab changes how the Back button works. Use a description instead of a long raw URL when possible.
Every link and button needs an accessible name. An image-only cart link needs text that supplies its destination; a picture with no alternative can leave a link unnamed or identified by a file name. Naming is as necessary as choosing the right role.
Keep built-in behavior
Use native checkboxes, radio buttons and select controls. They come with keyboard operation and announced states. A simple disclosure can use HTML alone:
<details>
<summary>Delivery options</summary>
<p>We deliver within 10 km.</p>
</details>
The summary receives keyboard focus and the disclosure exposes its open or closed state. A custom row with a triangle icon would need those features implemented separately.
Connect table data to its headers
Use tables for data with row and column relationships. A caption names the data set; th identifies headers; scope tells the browser which cells a header describes.
<table>
<caption>Shop opening hours</caption>
<tr><th scope="col">Day</th><th scope="col">Opens</th></tr>
<tr><th scope="row">Saturday</th><td>08:00</td></tr>
</table>
A person moving to the time cell can hear the associated day and column header. A CSS grid of div elements loses those built-in relationships. For page layout, use flexbox or grid; a layout table can create misleading table navigation where there is no actual data relationship.
Key Takeaways
- Use links for destinations and buttons for actions.
- Name links clearly and identify downloads or new tabs.
- Prefer native controls and disclosures for built-in behavior.
- Use captions, header cells and scope in data tables.
- Use CSS layout for page arrangement.
2Provide text alternatives for non-text content
Images, icons and media can carry information that is missing from the page’s ordinary text. Choose each alternative by its purpose: a product photo needs different words from a search icon, and a video needs more than an image description. Learn where short alternatives belong, when a longer explanation is needed and how captions, transcripts and audio description support different ways of using media.
2.1 Write alt text that fits each image's purpose
Alt text gives an image's useful information or function to someone who cannot see it. Decide what the image contributes on this particular page before describing its appearance. The same photograph can need different text in different contexts.
Choose by purpose
For an informative product photograph, alt="Round sourdough loaf with a dark, cracked crust" describes what a buyer needs to know. On a page about baking faults, the important feature might instead be a burnt crust. Imagine reading the page over the phone: include the information the listener would otherwise miss.
For an image that adds only decoration, use alt="". An empty alternative tells screen readers to skip the image. Omitting the attribute is different: a screen reader may announce a file name such as "IMG underscore 4032 dot jpg". A purely decorative background can also be placed in CSS.
Name a function, not a shape
When an image is the only content of a link or button, its alternative supplies the control's name. A home link can be:
<a href="/">
<img src="logo.svg" alt="Green Leaf Bakery home">
</a>
For a search button, use "Search", not "Magnifying glass". The user needs to know what pressing the control will do. If a product link already contains the visible words "Rye loaf", a photo in that same link can use alt="" so the link's name is not repeated.
Handle text and complex graphics
An image of text needs the same words in its alternative. A sale banner reading "Summer sale: 20% off all bread" needs those words, not "Sale banner". Prefer real HTML text styled with CSS when possible. It can resize, translate and reflow; WCAG 1.4.5 Images of Text (AA) requires real text when the technology permits it, with exceptions such as logos.
A chart, diagram or map often contains too much information for a short alternative. Give it a brief identification, then provide a fuller description nearby. For example, alt="Bar chart of monthly website visitors, January to June" identifies the graphic. Nearby text can explain that visitors increased from 1,200 to 3,000, while a data table provides individual values when those are needed. A <figure> with <figcaption> can group the graphic and its explanation.
Edit for useful detail
Keep ordinary alt text to a concise phrase or sentence. Screen readers already identify an image, so opening with "Image of" usually adds no useful content. Remove repeated adjacent wording and search keywords that do not help the person understand the page.
WCAG 1.1.1 Non-text Content (A) requires an equivalent purpose, not a description of every visible detail. The title attribute is not a reliable replacement: it is not consistently announced or available through keyboard and touch use. Put an image's alternative in alt and keep any longer explanation in the page itself.
Key Takeaways
- Write alt text for the image’s purpose in its page context.
- Use
alt=""for decoration; do not omit the attribute. - Functional images name the action or destination.
- Use nearby text or data for complex images.
- Prefer real HTML text to images of text.
2.2 Name icons and SVG graphics and give media text alternatives
An icon, a diagram and a video need different kinds of alternatives. Begin by identifying the information or action each contributes. Then put the equivalent where both the browser and the person using the page can find it.
Name the control that owns the icon
Visible text beside an icon is usually clearest. If a button must contain only an icon, supply visually hidden text or a label on the button:
<button type="button" aria-label="Close">✕</button>
When an icon sits beside visible button text, hide the decorative icon with aria-hidden="true". Do not hide the whole button. A plain span or div has a generic role on which naming is prohibited; putting aria-label there is not a dependable way to name an icon.
Visually hidden text remains available to screen readers. A reusable CSS class can use absolute positioning, a 1 px width and height, hidden overflow, clip-path: inset(50%) and white-space: nowrap. Unlike display: none or visibility: hidden, it keeps the text in the accessibility tree.
For a meaningful inline SVG, use role="img" and a name, for example aria-labelledby="rating-title" referring to <title id="rating-title">Rated 4 out of 5 stars</title> inside it. Decorative SVG uses aria-hidden="true". An SVG loaded through img gets its alternative from alt like other images. Keep meaningful warnings in HTML; CSS backgrounds and generated content are unreliable places for essential information.
Match the media alternative
| Content | Needed alternative |
|---|---|
| Audio-only recording | Transcript |
| Prerecorded video with sound | Captions; audio description for important unspoken visuals at AA |
| Live video with sound | Live captions at AA |
Captions synchronize speech, important sounds and speaker identification with the video. Subtitles transcribe or translate dialogue for people who can hear but need help understanding the language. A transcript is a separate text account; it does not follow the playback time.
<video controls src="bread.mp4">
<track kind="captions" src="bread-en.vtt"
srclang="en" label="English">
</video>
The WebVTT file contains timed cues, such as 00:00:01.000 --> 00:00:04.000, followed by caption text. Check automatic captions for names, technical words and punctuation before publishing.
If a demonstration silently shows an oven setting, captions of the speech do not convey it. Audio description narrates important visual information in pauses or in a described version. A full text alternative including visuals is an option at level A; prerecorded video needs audio description at AA under 1.2.5.
Keep playback usable
Use native controls on audio and video, and give embedded frames a descriptive title, such as "Map of our shop location". Avoid autoplaying sound. Audio that starts automatically and lasts more than 3 seconds needs pause, stop or volume control independent of the overall system volume.
Connect the hidden name and timed text
For a compact delete button, keep its words in the document:
<button type="button">
<img src="trash.svg" alt="">
<span class="visually-hidden">Delete item</span>
</button>
The image is skipped because the text already supplies the action. Apply the hiding class without removing that text from the accessibility tree:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
A caption file instead connects words to playback time. A small WebVTT example is:
WEBVTT
00:00:01.000 --> 00:00:03.000
[Mixer whirring]
00:00:03.000 --> 00:00:06.000
Today we bake rye bread.
The first cue conveys a relevant sound without dialogue. The second conveys speech during its own interval. Watch the video with the track enabled and check that each cue appears when its sound occurs.
Key Takeaways
- Name icon controls on the control itself.
- Visually hidden text remains available to assistive technologies.
- Give meaningful SVG graphics a role and name.
- Captions include speech and important sounds; transcripts support audio-only recordings.
- Audio description supplies important visual information missing from a video’s soundtrack.
- Keep player controls available and name embedded frames.
3Make every function work with a keyboard
Keyboard operation depends on a clear route through the page and a visible current position. Work with native controls, keep the order meaningful and prevent focus from landing on invisible content. Add shortcuts past repeated navigation, and preserve the user’s place when a dialog opens or an item disappears. Check control size and focus visibility so the same interface is easier to operate in several ways.
3.1 Operate a page by keyboard in a logical focus order
Keyboard users need a route through the whole task: reach a control, recognize it, operate it and move on. Standard HTML controls already support this route. Problems often appear when styling or custom controls interfere with the browser's behavior.
Use the expected keys
Tab advances through interactive elements; Shift+Tab goes backward. Enter follows links and activates buttons. Space activates buttons and toggles checkboxes. In a radio group, Tab enters or leaves the group and arrow keys move between options. Arrow keys also work in native select controls. Escape closes dialogs and many menus.
Links with href, buttons, inputs, selects, textareas, summaries and media with controls are focusable by default. Paragraphs, headings, images, generic containers and anchors without href are not ordinary Tab stops. A screen reader has separate reading commands for text, so adding every heading to the Tab sequence makes keyboard navigation slower.
On macOS, enable keyboard navigation when testing. Safari's "Press Tab to highlight each item on a webpage" setting controls whether ordinary Tab navigation reaches links; Option+Tab can reach them when that setting is off.
Keep tabindex values simple
| Value | Effect |
|---|---|
tabindex="0" | Adds a Tab stop in source order |
tabindex="-1" | Allows focus by script or as a link target; Tab skips it |
| A positive value | Moves a stop before ordinary controls according to the number |
Positive values are hard to maintain because they create a second ordering system. Write the HTML in a logical order and avoid them. A heading that must receive focus after an update can use -1 without becoming a stop on every trip through the page.
Check meaning and behavior together
<div role="button" tabindex="0">Save</div>
This exposes a button role and receives focus, but it still needs script for Enter and Space, an action and a visible focus style. Replacing it with <button type="button">Save</button> supplies native keyboard activation without rebuilding it. ARIA alone cannot create the interaction.
CSS can also disturb navigation. If the source order is logo, menu, search, sign-in link, CSS order: -1 can show sign-in first while Tab still reaches it last. Move content in the HTML when the reading order needs to change. WCAG 2.4.3 Focus Order (A) requires an order that preserves meaning and operation.
Remove traps and hidden stops
Test whether focus can leave every component using the keyboard. A map or player that accepts focus but offers no keyboard exit creates a keyboard trap, prohibited by 2.1.2 No Keyboard Trap (A).
A closed menu moved off-screen or given opacity: 0 may still contain focusable links. Use hidden, display: none or inert for content that must be unavailable while closed. Keep an off-screen skip link focusable, however: it has a deliberate rule that brings it into view when focused.
Key Takeaways
- Use standard keyboard behavior for links and native controls.
- Keep Tab stops for interactive content.
- Use zero for source-order stops and minus one for deliberate focus management.
- Avoid positive tabindex values and visual reordering that confuses navigation.
- Test for keyboard traps and focus landing on hidden content.
3.2 Show focus clearly and make controls easy to hit
Focus is the keyboard user's current position. A visible ring tells the person which control Enter or Space will operate. The ring needs to remain visible against the page and the focused control needs to stay in view.
Keep a clear indicator
Browsers supply a default focus outline. Removing it with outline: none loses an essential cue unless a suitable replacement is provided. Use :focus-visible to show a ring when the browser judges it useful, including keyboard navigation and text fields:
:focus-visible {
outline: 3px solid #1A73E8;
outline-offset: 2px;
}
The blue has 4.51:1 contrast against white. Check the actual adjacent colors in the design; a ring that works on white may blend into a blue panel. WCAG 2.4.7 Focus Visible (AA) requires visible keyboard focus, while 1.4.11 Non-text Contrast (AA) applies to the visual information identifying a custom state, including a custom focus indicator, at 3:1.
An outline is a useful choice for Windows contrast themes. Forced colors can replace backgrounds and remove box shadows, while outlines remain available. A shadow alone can therefore disappear in a mode a person relies on.
Keep the focused control in view
Sticky headers, banners and chat controls can cover focus. Under 2.4.11 Focus Not Obscured (Minimum) (AA), author-created content must not entirely hide the focused component. Keeping the whole component visible is easier to use; the no-part-hidden requirement belongs to AAA.
For a sticky header 5rem high, allow scrolling space:
html { scroll-padding-top: 5rem; }
Then Tab through the actual page. Check both directions and test when a cookie notice is present. The CSS rule is a starting point; it still needs to work with the page's real header and viewport sizes.
Give pointer users room too
Target Size (Minimum), 2.5.8 (AA), requires pointer targets at least 24 by 24 CSS px, with exceptions. A smaller target can pass if a 24 px diameter circle centered on it intersects neither another target nor another small target's circle. Inline sentence links, unmodified browser-sized controls and targets with an equivalent compliant control on the page are also exempt. An essential or legally required presentation can be exempt as well.
For three 16 by 16 px icon buttons separated by 4 px gaps, the circles would overlap. Adding 4 px padding on all sides makes each target 24 by 24 px. Measure the clickable area, not just the icon drawn inside it.
The 44 by 44 CSS px target in 2.5.5 and the focus-area size requirement in 2.4.13 are AAA requirements. They are useful improvements, but keep their level separate from the AA minimum when reporting a finding.
Key Takeaways
- Keep a visible focus indicator on every keyboard-operable element.
- Check custom indicator contrast against adjacent colors.
- Prevent sticky content from entirely hiding the focused component.
- Measure the whole pointer target; AA normally requires 24 by 24 CSS px or a qualifying exception.
3.3 Add skip links and manage focus when content changes
Repeated menus should not make people repeat the same journey on every page. A skip link provides a short route to the main content. When an interaction changes the page, deliberate focus handling also helps people keep their place.
Build and test a skip link
Place the skip link before the repeated header controls:
<a class="skip-link" href="#main">Skip to main content</a>
<main id="main" tabindex="-1">
<h1>Order a birthday cake</h1>
</main>
The link targets the main area, and tabindex="-1" helps that area receive focus without adding an ordinary Tab stop. A screen reader's landmark navigation helps its users; the skip link also helps sighted keyboard users bypass repeated blocks under 2.4.1 Bypass Blocks (A).
Keep the link available to the keyboard when it is visually tucked away:
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 1rem; top: 1rem; }
Do not use display: none or visibility: hidden, which prevent focus. Reload, press Tab once and check that the link appears. Press Enter, then Tab again: navigation should continue at the main content's first link or control, not at the header menu.
Give changed content a sensible next position
If a cart row and its focused Remove button disappear together, focus can fall back to the page start. Move it deliberately to the next item's Remove button or to a cart heading that has tabindex="-1". Choose a place that continues the task. Merely scrolling to a region does not ensure that keyboard focus moved there.
Use a native modal dialog
A dialog opened with showModal() moves focus inside and makes the rest of the page inert. Tab stays inside while the dialog is open; Escape closes it. Closing normally returns focus to the opener when it still exists.
<button type="button"
onclick="document.getElementById('hours').showModal()">Opening hours</button>
<dialog id="hours" aria-labelledby="hours-title">
<h2 id="hours-title">Opening hours</h2>
<p>Tuesday to Saturday, 08:00–17:00.</p>
<form method="dialog"><button>Close</button></form>
</dialog>
The one-line script finds the dialog by its ID and opens it modally. The dialog form closes it without another script. Its heading supplies its name. A homemade overlay needs all of these interaction rules recreated; making the background look dark does not stop keyboard focus from reaching it.
Wait for an intentional action
Receiving focus must not open a new page or otherwise change context. Changing a selection must not unexpectedly do so either; users must be told in advance if that behavior occurs. A "Go" button after a destination selector gives people time to inspect and change their selection before navigating. These behaviors support 3.2.1 On Focus (A) and 3.2.2 On Input (A).
Key Takeaways
- Make the skip link the first focusable item and reveal it on focus.
- Test where the next Tab goes after using the skip link.
- Move focus deliberately when a focused item is removed.
- Use
dialogwithshowModal()for native modal behavior. - Avoid unexpected context changes on focus or selection.
4Keep text and visuals readable
People need to read and operate a page under different viewing conditions. Measure foreground and background colors, pair color with another cue and build layouts that expand when text grows. A readable desktop view is only the starting point: enlarged text, narrow reflow and user spacing can reveal hidden problems. The checks here connect specific visual requirements to practical CSS changes.
4.1 Measure and meet color contrast requirements
Contrast measures how clearly foreground information stands out from its background. A color that looks readable on one screen can be difficult for someone with low vision or in bright light. Measure the pair instead of judging it by eye.
Apply the correct minimum
| What you are checking | WCAG AA minimum |
|---|---|
| Normal text | 4.5:1 |
| Large text | 3:1 |
| Visual information identifying a control, its state or a meaningful graphic | 3:1 against adjacent colors |
Large text means at least 18 point, equivalent to 24 CSS px, or at least 14 point bold, about 18.7 CSS px bold. A 20 px regular-weight heading still needs 4.5:1. Placeholder, footer, hover and focus text are not exempt from text contrast. Logos, inactive controls and incidental or purely decorative text have exceptions.
Understand the ratio
Relative luminance expresses brightness on a scale from 0 for black to 1 for white. With the lighter luminance as L1 and darker as L2, the ratio is:
The extremes are 1:1 for identical colors and 21:1 for black against white. A contrast checker calculates luminance from the color channels and compares the ratio with the relevant threshold.
For gray #767676, each channel is hexadecimal 76, or 118. Divide by 255 to get about 0.4627. Because this is above 0.04045, convert it to linear light:
The three channels are equal, so their weighted luminance is also about 0.1812. White has luminance 1. Keeping full precision through the calculation gives 4.54:1, which passes normal text. Use a checker for actual color pairs so rounded intermediate values do not affect a close result.
Never round a failure into a pass
Gray #777777 on white gives 4.48:1. Displaying it to one decimal as 4.5 does not make it pass. Likewise, #949494 on white gives 3.03:1 and passes for large text; #959595 gives 2.99:1 and fails for large text.
White on #0066CC gives 5.57:1. On amber #FFC107, white text gives only 1.63:1, while black gives 12.88:1. Choosing a bright button background does not tell you whether its text should be light or dark.
Check boundaries and every background
An input border that is needed to identify the field must meet non-text contrast. #CCCCCC against white gives 1.61:1 and fails; #949494 gives 3.03:1 and passes. The same 3:1 rule applies to meaningful icons, chart lines and custom focus indicators. Inactive and unmodified browser-default controls have exceptions.
Text over a photograph or gradient must pass at the weakest spot behind it. Add a solid or sufficiently opaque panel, or move the text. Check the resulting colors with developer tools or a contrast checker. WCAG 1.4.3 covers text and 1.4.11 covers non-text information; AAA text thresholds are higher, at 7:1 for normal and 4.5:1 for large text.
Check a button's parts separately
Suppose a button has white normal-size text on #0066CC, sits on a white page and uses a #CCCCCC custom focus ring. Check each relationship:
- The label against the blue button is 5.57:1, above the 4.5:1 text minimum.
- The blue button against the white page is also 5.57:1, so its boundary is distinguishable.
- The light gray focus ring against the white page is only 1.61:1. That state indicator fails even though the label passes.
Replace the ring with a color that reaches 3:1 against its adjacent background, then measure again. A checker reading for the button label does not also measure every boundary or interaction state. Treat the text, component and custom state as separate color-pair checks.
Key Takeaways
- Normal text needs 4.5:1; large text needs 3:1 at AA.
- Large text is defined by size and weight, not by its heading role.
- Never round a result up to a passing threshold.
- Important control boundaries, states and graphics need 3:1 against adjacent colors.
- Check text over images at its weakest background spot.
- Use a contrast checker to measure the actual color pair.
4.2 Use color together with other cues
Color can draw attention, but it cannot carry the only visible clue to meaning. People may have a color vision deficiency, use grayscale or contrast settings, or hear content through a screen reader without color announcements. Pair color with text, shapes, patterns or another clear cue.
Separate two checks
Contrast asks whether a foreground can be distinguished from its background. Use of color, WCAG 1.4.1 (A), asks whether the information remains understandable without recognizing the color. A dark red label on white might pass contrast and still fail to explain which field has an error.
Start with the message. If the page says "Choose a green option", give each option a useful name too. If a red dot means a shop is closed, add "Closed now" beside it. The text carries the status, while the color helps people notice it.
Keep links recognizable
The simplest way to identify a link in a paragraph is an underline. It gives a cue even when the link and surrounding text are seen in grayscale.
A color-only difference from surrounding text needs at least 3:1 contrast between the two text colors and an additional cue, such as an underline, on hover and focus. The link text must also meet text contrast against its background. These are separate comparisons; a blue that passes against white may be too similar to the neighboring dark paragraph text.
Explain errors and required fields
For an email error, show "Error: Enter an email address in the format name@example.com" beside the field. A red outline can remain as an extra cue. The text identifies the problem and tells the user how to fix it, while the code connects it to the field.
For required fields, use a label such as "Email address (required)". An asterisk also works when the start of the form explains "* means required". Add the native required attribute so the requirement is available to assistive technology and browser validation. Red label text by itself supplies neither an adequate visible explanation nor a programmatic requirement.
Give graphics a second way to communicate
A sales chart with two series needs more than a red line and a green line. Direct labels, different line styles or patterns let people trace each series without identifying its hue. Use these cues in the chart and in its key so they match.
Check the page with grayscale or color-vision emulation in developer tools. Inspect running-text links, errors, required fields, charts and availability indicators. Ask what visible clue remains for each distinction. If a distinction disappears, add a cue that states or depicts its meaning; increasing saturation alone will not supply the missing information.
Key Takeaways
- Pair meaningful color differences with another visible cue.
- Check contrast and use of color separately.
- Underline links or meet both color-difference and interaction-cue conditions.
- Use text for errors, required fields and status indicators.
4.3 Keep pages usable when zoomed or when text is enlarged
Enlarging a page should make it easier to use without hiding controls or cutting off sentences. People may zoom the whole page, enlarge only text or apply their own spacing. Flexible layouts allow these choices to work together.
Test text size and reflow
WCAG 1.4.4 Resize Text (AA) requires text to enlarge to 200% without loss of content or functionality. Browser zoom uses Ctrl and plus on Windows or Cmd and plus on macOS. Also test a larger default font size, which changes text without enlarging everything else in the same way.
WCAG 1.4.10 Reflow (AA) requires ordinary content to work at 320 CSS px wide without scrolling in two directions. A 1280 CSS px wide window at 400% zoom provides that effective width. Paragraphs should wrap into a narrow column, with all functions still available. Data tables, maps and other content whose meaning needs two dimensions have exceptions.
Let the layout grow with the text
Use relative font units such as rem and em so text follows the user's default font setting. Text in CSS pixels still responds to browser zoom, but does not follow that default setting in the same way. Flexible containers and max-width allow lines to wrap; breakpoints in em can adapt as text grows.
A fixed height can silently cut off content:
/* Problem when larger text needs more space */
.card { height: 200px; overflow: hidden; }
/* Content can grow beyond the minimum */
.card { min-height: 200px; }
Allow the content to set its height. At narrow widths, stack navigation or offer an accessible disclosure instead of deleting links. Let an oversized sticky header scroll away if it consumes too much of the usable view.
Keep mobile zoom available
Use a viewport declaration that follows the device width:
<meta name="viewport" content="width=device-width, initial-scale=1">
Do not add maximum-scale=1 or user-scalable=no. They attempt to block pinch zoom, which people with low vision may need. Some browsers ignore those restrictions, but users should not have to depend on that behavior.
Apply the user's spacing
WCAG 1.4.12 Text Spacing (AA) requires no content loss or overlap when users set line height to 1.5 times font size, paragraph spacing to 2 times, letter spacing to 0.12 times and word spacing to 0.16 times. These are test settings, not mandatory default design values. Use a text-spacing tool and check labels, cards, menus and buttons as well as paragraphs.
A practical check combines a 200% text test, a 320 CSS px reflow test and the spacing override. Look for clipped lines, overlapping labels, horizontal paragraph scrolling and functions that disappeared. Fix the container or layout causing the problem; shrinking the user's enlarged text removes the benefit they asked for.
Key Takeaways
- Keep text and functionality available at 200% enlargement.
- Check ordinary content at 320 CSS px wide for reflow.
- Use relative font sizes and containers that can grow.
- Allow pinch zoom and retain functions in narrow layouts.
- Test user text-spacing overrides for clipping and overlap.
5Build accessible forms
An accessible form explains what to enter, makes each control easy to identify and helps people recover from errors. Connect visible labels and instructions in HTML, use the browser’s input and autofill features and preserve answers across a process. When a message appears, decide whether focus should move to it or whether an announcement can let the user continue working in place.
5.1 Label and group every form control
A visible label tells a person what to enter and gives a field an accessible name. Group labels add the question that several choices answer. The connections must exist in HTML, even when labels and fields already appear close together.
Connect a label to one control
An explicit label uses for to reference the control's unique id:
<label for="email">Email address</label>
<input id="email" name="email" type="email">
The browser can announce "Email address" on focus. Clicking the text also focuses the field, giving users a larger target. The name attribute identifies submitted data; it does not replace the id used by the label.
An implicit label wraps its input: <label>Email address <input type="email" name="email"></label>. This works when both fit inside the same label element. With separated layout containers, the explicit connection is easier to maintain.
A placeholder disappears during typing and often has low contrast. Keep the label visible, and place examples or format hints nearby. "Field 1" is technically a name but does not describe the purpose. WCAG 2.4.6 Headings and Labels (AA) requires descriptive wording.
Keep the visible words in the name
A person using voice control may say "click Send" after seeing a Send button. If aria-label="Submit form" overrides that button's text, its accessible name no longer contains the visible words. WCAG 2.5.3 Label in Name (A) requires those words to be included, ideally at the start. Use the visible text itself when possible: <button type="submit">Send message</button>.
A lone search field may have a visually hidden label if a nearby Search button makes its purpose visually clear. Hidden labels are not a general replacement for visible instructions throughout a form.
Group choices under their question
<fieldset>
<legend>Delivery method</legend>
<input type="radio" id="pickup" name="delivery" value="pickup">
<label for="pickup">Pick up in store</label>
<input type="radio" id="courier" name="delivery" value="courier">
<label for="courier">Courier delivery</label>
</fieldset>
The legend provides the group question; each radio still needs its own label. Screen readers can announce the group context with an option. Use the same radio name for mutually exclusive choices, and short legends that remain useful when repeated. Related checkboxes can also use a fieldset and legend.
Choose types and check connections
Use email, tel, url and date for the matching data. Email and URL types provide a format check; telephone inputs can offer a suitable mobile keyboard but do not validate phone-number formats. Use number for quantities such as guest counts, not phone or card identifiers.
Click each label to test its connection. If the wrong field receives focus, check for duplicate IDs or a mismatched for. Inspect the computed name in developer tools as well; visual proximity alone cannot connect a label.
Key Takeaways
- Connect each label to a unique input ID or wrap its one control.
- Keep labels visible; use placeholders only as supplementary content.
- Keep the visible label text inside the accessible name.
- Use fieldset and legend for a related choice group.
- Choose input types for the kind of data, and check label connections.
5.2 Give instructions, mark required fields and support autofill
Instructions help most when they arrive before an error. A good form explains the expected input, shows what is required and supports the browser's tools for entering personal information. It also avoids making users repeat work unnecessarily.
Connect a hint without replacing the label
<label for="pw">Password (required)</label>
<input id="pw" type="password" required
autocomplete="new-password" aria-describedby="pw-hint">
<p id="pw-hint">At least 12 characters.</p>
The label names the field. The visible paragraph explains its requirement, and aria-describedby adds that text as a description. Screen readers can announce it after the name. For several descriptions, list their IDs separated by spaces, such as aria-describedby="pw-hint pw-error".
Mark required fields visibly and with required. You can write "(required)" or explain an asterisk at the start of the form. Native required supports browser validation and assistive technology; it does not need an extra aria-required="true".
Identify the purpose of personal data
Under 1.3.5 Identify Input Purpose (AA), fields collecting the user's information need appropriate autocomplete tokens. Examples include name, given-name, family-name, email, tel, street-address, postal-code, country-name and bday. Login fields can use username and current-password; a new password uses new-password, and a verification code can use one-time-code.
These tokens let browsers, password managers and assistive tools recognize a field's purpose. Use the matching token for the user whose information is being entered. Marking a friend's contact address as the user's own autofill email can fill the wrong details.
Reduce memory and repeat-entry demands
Allow paste in password and verification-code fields and support password managers. WCAG 3.3.8 Accessible Authentication (Minimum) (AA) permits cognitive tests during login only with a qualifying alternative or assistance, or specified object-recognition or personal-content exceptions. Blocking paste creates a barrier when it forces recall or transcription and no qualifying alternative login route exists.
For a multistep order, carry forward information already entered. A "Billing address is the same as delivery address" choice avoids retyping. WCAG 3.3.7 Redundant Entry (A) allows exceptions when repetition is essential, needed for security or the earlier information is no longer valid.
Provide a safeguard for consequential submissions
An order can create a financial commitment. Before completing it, offer a review step that lets users correct details. Under 3.3.4 Error Prevention (Legal, Financial, Data) (AA), relevant submissions must be reversible, checked with a chance to correct, or reviewed and confirmed with correction available. The scope also includes changing or deleting user-controlled stored data and submitting test answers.
Keep the form's layout easy to follow when enlarged: labels directly above fields and one main column reduce the distance between an instruction and the place it applies.
Distinguish format from personal purpose
The input type and the autocomplete token answer different questions. The type identifies the kind of value and may supply a format check. The token identifies the user's personal information for autofill:
<label for="your-email">Your email address (required)</label>
<input id="your-email" name="email" type="email"
required autocomplete="email">
<label for="guest-email">Guest's email address</label>
<input id="guest-email" name="guest-email" type="email">
Both fields accept email addresses, but only the first asks for the current user's email. The second should not be marked as that user's email for autofill. The visible labels preserve the distinction for people entering the values themselves. Changing a token does not change what the email type checks.
Key Takeaways
- Give visible instructions before input and connect hints with
aria-describedby. - Mark required fields both visibly and with native
required. - Use appropriate autocomplete tokens for the user’s information.
- Support password managers and paste to reduce login barriers.
- Reuse earlier answers in multistep processes unless an exception applies.
- Provide an appropriate error-prevention safeguard for consequential submissions.
5.3 Report errors and status messages clearly
An error message should help a person recover without losing the work already done. A status message should report a change without stealing the person's place. Both need visible wording and the right programmatic connection.
State the problem and a useful correction
"Invalid input" gives little help. "Error: Enter an email address in the format name@example.com" identifies what needs attention and suggests a fix. WCAG 3.3.1 Error Identification (A) requires detected errors to be described in text; 3.3.3 Error Suggestion (AA) requires known corrections when they do not undermine security or the form's purpose.
<label for="email">Email address</label>
<input id="email" type="email" aria-invalid="true"
aria-describedby="email-error">
<p id="email-error">Error: Enter an email address in the format name@example.com</p>
Set aria-invalid="true" only after checking the field and finding an error. The attribute reports the invalid state; the description explains the reason. A red border alone supplies neither the text explanation nor the connection.
Help people find several errors
After submission, a long form can show an error summary at the top. Give it a heading such as "There are 2 problems with your order", then links to the affected fields. Make the summary focusable with tabindex="-1" and have the script focus it. Prefixing the page title with "Error: " also helps identify the changed page state.
Keep field-level messages beside their inputs as well, and preserve everything already entered. A summary link gets a person to a field; the local explanation helps while they correct it. Clearing every field forces them to repeat successful work.
Native validation attributes such as required, type="email" and minlength can supply browser messages. A custom system may use novalidate and show its own. In either case, errors need text that users can locate and associate with the relevant field.
Announce a change without moving focus
A cart update often happens while focus should stay on the Add button. Use a live region already present in the document:
<p role="status" id="cart-status"></p>
When an item is added, a script changes its text to "Added 1 sourdough loaf to your cart". role="status" is polite: it lets the screen reader finish its current speech before announcing the update. This supports 4.1.3 Status Messages (AA).
Reserve role="alert", an assertive announcement, for urgent messages. Continual interruptions make ordinary shopping harder. Insert the empty region before updating its contents; creating the region and its message together is not announced reliably in many combinations. Keep focus on the task for routine updates, and use deliberate focus movement for situations such as an error summary that the user needs to inspect.
Key Takeaways
- Identify errors in text and suggest a correction when known.
- Use
aria-invalidfor a checked failure and connect its error description. - Keep entered data and provide an error summary for longer forms.
- Update an existing status region to announce routine changes without moving focus.
- Reserve alerts for urgent messages.
6Use ARIA sparingly and test your pages
ARIA can describe information that native HTML does not express on its own, but its promises must match the interface’s behavior. Learn to choose names and states carefully, then inspect what the browser reports. Combine automated scans with keyboard, zoom and screen reader tests to find both coding errors and barriers that need human judgment. A useful finding includes a clear way to reproduce and retest it.
6.1 Apply the rules for using ARIA
ARIA adds information to the accessibility tree when HTML needs help expressing a name, relationship or state. It does not build the interaction itself. Before adding an attribute, check what the native element already provides and what is actually missing.
Treat a role as a promise
WAI-ARIA stands for Accessible Rich Internet Applications. Version 1.2 became a W3C Recommendation in June 2023. Its roles, states and properties influence what assistive technologies are told. A role such as button promises button behavior, but does not create focusability, keyboard activation or styling.
A div with role="button" still needs keyboard handling and focus support. A real button already supplies them. The ARIA Authoring Practices Guide's principle "no ARIA is better than bad ARIA" warns that incorrect information can mislead users about what a control does.
Preserve native meaning
Use native elements and attributes first: nav, button, a checkbox input and native required. Avoid repeating implicit roles such as role="main" on main or role="button" on button. One useful exception is role="list" on a list whose removed bullets affect VoiceOver announcements.
Do not replace a heading's meaning with a button role. Nest a button inside it:
<h2>
<button type="button" aria-expanded="false">Shipping</button>
</h2>
The heading still appears in heading navigation and its button exposes the action. The script must make the shown content and expanded state agree.
Never put aria-hidden="true" on a focusable control or an ancestor containing one. Focus could land on something removed from the accessibility tree. role="presentation" and role="none" work differently: they remove semantics, not the content. Browsers ignore those roles on focusable elements, so they cannot be used to turn a live link into plain text.
Find the source of a name
For the ordinary controls here, check name sources in this priority order:
aria-labelledby, referring to text by ID.aria-label, providing a string.- The native source, such as a label, button text or image alt text.
title, as a last resort where applicable.
aria-describedby adds a description after the name; it does not set the name. An override can accidentally hide useful visible wording. <button aria-label="Close dialog">Cancel</button> is announced as "Close dialog", although the person sees "Cancel". Prefer visible text, or include it at the start of a necessary expanded name.
Basic sites commonly need ARIA for repeated landmark names, icon buttons, field hints and errors, disclosures, current-page links and live updates. A large collection of ARIA attributes is not a measure of quality. Inspect the computed role and name, and use the keyboard to verify the behavior that the role promises.
Trace competing name sources
Consider a button with several naming attributes:
<h2 id="options-title">Delivery options</h2>
<button type="button" aria-labelledby="options-title"
aria-label="Choose delivery">Show pickup options</button>
First follow aria-labelledby: its referenced text supplies "Delivery options". The lower-priority aria-label and visible button text do not win. Removing only aria-label therefore leaves the same computed name.
To let "Show pickup options" name this button, remove both unnecessary overrides. The button role stays the same throughout; only its name source changes. Inspect the computed name after each edit so you can tell which attribute caused the mismatch. Adding a description would not repair the wrong name, because a description is a separate piece of information.
Key Takeaways
- Prefer native HTML meaning and behavior before adding ARIA.
- ARIA changes the accessibility tree, not keyboard handling or styling.
- Preserve native semantics when adding interactive content.
- Never hide a focusable element or its ancestor with
aria-hidden. - Check name overrides; descriptions do not replace names.
6.2 Communicate changing states with ARIA
A changing interface has to tell the same story visually and programmatically. If a panel is open but its button still reports "collapsed", a screen reader user receives false information. Update the state whenever the interface changes.
Keep a disclosure in sync
<button type="button" aria-expanded="false"
aria-controls="main-menu">Menu</button>
<ul id="main-menu" hidden>
<li><a href="/bread">Bread</a></li>
</ul>
When the menu opens, the script removes hidden and sets the button's aria-expanded to true. When it closes, the script restores hidden and sets the state to false. The state belongs to the button doing the toggling. aria-controls identifies the affected element; its support varies, so it cannot replace the expanded state or actual hiding behavior.
Choose the state that fits the control
| Situation | Attribute |
|---|---|
| A control opens or closes content | aria-expanded |
| A navigation link identifies the current page | aria-current="page" |
| A button stays on or off | aria-pressed |
For the current page link, style the same attribute in CSS, such as a bold font and bottom border on a[aria-current="page"]. That keeps the visible highlight tied to the announced state. aria-selected belongs to widgets such as tabs and listbox options, not ordinary page-navigation links.
A toggle button can keep the label "Mute" and change aria-pressed between false and true. Another valid design changes a normal action button's label between "Mute" and "Unmute" without a pressed state. Do not combine a changing action label with the toggle-state pattern, which can produce confusing announcements such as "Unmute, pressed".
Distinguish hiding from disabling
aria-hidden="true" removes an element and its descendants from the accessibility tree but leaves them visible. Use it for decorative icons or redundant visual content. It does not stop keyboard access. Use hidden or display: none when a region should be unavailable to everyone.
Native disabled prevents ordinary interaction with a native control. aria-disabled="true" reports an unavailable state but does not block clicks or keyboard activation. If that attribute is used to keep a disabled control focusable for explanation, the script must prevent its action. Prefer native checked and required on native inputs too.
Test both directions
Open and close the disclosure using the keyboard while watching the attributes in developer tools. Inspect the computed state and listen to its announcement. A successful visual change alone does not show that the state updated, and a changed attribute alone does not show that the panel became visible or usable.
Key Takeaways
- Update ARIA states whenever the corresponding interface state changes.
- Put
aria-expandedon the disclosure control. - Use
aria-current="page"for current-page links. - Keep a pressed-state toggle’s label stable.
- ARIA hiding and disabling do not supply keyboard or click behavior.
6.3 Run automated accessibility checks and interpret the results
Automated checks quickly find many coding problems across a rendered page. Use their results to locate barriers and verify repairs. Their rules can inspect some relationships and values, but deciding whether content makes sense still needs a person.
Choose a checker and inspect its findings
Axe DevTools uses the axe-core engine. WAVE places markers on the page so findings can be seen in context. Lighthouse in Chrome developer tools gives an accessibility score using a selection of axe-core checks. Accessibility Insights provides another testing workflow. The W3C Markup Validation Service checks HTML problems such as duplicate IDs and unclosed elements.
Depending on their rules and context, tools can detect missing alt attributes, unnamed controls, missing field labels, low text contrast on plain backgrounds, missing page language and invalid ARIA. Some heading-level issues appear as warnings. Read the finding's explanation and inspect the element it identifies.
An alert or "needs review" result asks for judgment. Bold text flagged as a possible heading might be a genuine section title written as a paragraph, or it might simply emphasize a sentence. Examine its purpose before changing its markup.
Understand what the scan cannot decide
A checker can see alt="photo" without knowing that the alternative is useless. It cannot reliably judge clear link wording, caption accuracy, sensible focus order or whether an error message helps someone recover. A visual overlay may trap the keyboard in a way the current scan does not detect. Text over a photograph also needs manual contrast checking.
A score of 100 means the tested rules found no failures. It is not a WCAG conformance verdict. Keep testing tasks and meanings that those rules cannot evaluate.
Test the states people use
A scan examines the rendered state at that moment. Run checks on each page template and again with menus and dialogs open, after form errors and at narrow widths. A closed component can hide problems that only appear during use.
Suppose a bakery report finds a hero image without alt, a newsletter input without a label and footer text #999999 on white. First decide whether the image informs or decorates. Add a useful alternative or alt="" accordingly. Connect the email field to a visible label. The footer pair has 2.85:1 contrast; replacing the text color with #595959 gives 7.00:1 on white.
Fix causes and verify results
Prioritize barriers by their effect on the task. A keyboard trap or unnamed required control can stop an order completely. Repair a shared field or footer component at its source so every page using it improves. Then rerun the scan in the same state and repeat the relevant manual test. A lower error count is useful evidence, but the repair must also let the person complete the task.
Key Takeaways
- Use automated tools to locate machine-detectable problems.
- Inspect alerts and review items before deciding on a fix.
- A clean report covers only the checks the tool can perform.
- Scan page templates and interactive states, not just initial pages.
- Prioritize task blockers, fix shared causes and retest.
6.4 Test by hand with a keyboard, zoom and a screen reader
Manual tests show how a page behaves during a real task. Combine keyboard use, enlarged views, screen reader checks and direct tree inspection. Record enough detail that another person can reproduce a problem and confirm its repair.
Follow a keyboard task from start to finish
Put the mouse aside. Start from the address bar and Tab through the page. Reach every link and control, use Enter and Space where appropriate, and reverse direction with Shift+Tab. Check that focus stays visible and is not covered by a sticky region.
Use the skip link, open and close menus, close a dialog with Escape and verify where focus returns. Look for invisible stops in closed components and for traps in embedded players or maps. On a Mac, enable keyboard navigation and Safari's full Tab navigation before treating skipped controls as a page defect.
Enlarge and reflow the page
Test at 200% enlargement without losing text or functionality. Then use a 1280 CSS px wide window at 400% zoom, equivalent to 320 CSS px of available width. Check paragraphs for horizontal scrolling, overlaps and clipping. Keep every function available in the narrow layout. Apply the text-spacing values with a tool and inspect controls as well as body text.
Listen to the structure and names
NVDA on Windows uses Insert as the default NVDA key; Caps Lock is an optional alternative. In reading mode, H moves through headings, D through landmarks, K through links, F through fields, T through tables and B through buttons. NVDA+F7 opens the elements list. Tab still moves through interactive controls.
On macOS, Cmd+F5 toggles VoiceOver. Its VO modifier is Control+Option; VO+Right Arrow reads the next item and VO+U opens the rotor. On iPhone, swipe right moves to the next VoiceOver item. TalkBack offers similar touch reading on Android.
Listen for a useful page title and outline, clear control names, correct roles, useful image alternatives and understandable field labels. Required states, hints and errors should be available on the relevant fields. Open a disclosure and hear its state change. Add an item and listen for a status update without a focus jump.
Inspect, record and retest
The Accessibility pane in Chrome and Accessibility Inspector in Firefox show computed roles, names and states. They help trace why a label is wrong even before running a screen reader.
A useful report names the location, steps, observed behavior, affected users, related criterion and suggested fix. For example: "Newsletter field in the footer: Tab to the input; it is announced without a label. Screen reader and voice control users cannot identify its purpose. Add a connected Email address label; check 1.3.1, 3.3.2 and 4.1.2."
Retest with the method that found the issue. Common combinations include NVDA with Firefox or Chrome, JAWS with Chrome and VoiceOver with Safari. Testing with people who use assistive technology daily can reveal barriers that occasional developer use misses.
Make a state failure reproducible
A report for a disclosure can record a complete short test:
- On the bread page, use NVDA with Firefox and Tab to "Delivery options".
- Press Enter once. The panel becomes visible, but the button is still announced as collapsed.
- Inspect the button:
aria-expandedis stillfalse.
The affected user is told that the visible panel remains closed. Record 4.1.2 Name, Role, Value (A) and suggest updating the button's state when the panel changes. After the repair, repeat opening and closing with the same keys and combination. Check that the panel's visibility and the announced expanded state agree in both directions; inspecting only the initial collapsed state would miss this failure.
Key Takeaways
- Test a complete task with keyboard operation in both directions.
- Check enlargement, reflow and user spacing.
- Listen to structure, names, states, errors and live updates.
- Use the accessibility tree to trace computed information.
- Record reproducible findings and retest the repair with the same method.
- Include daily assistive-technology users when testing is available.
Building Accessible Web Pages examples
1Build a page that explains itself
12 steps
- 1IntroductionRead the page through its code
A bakery page looks clear: a large title, a menu, some bold section names and a Save control. Now check whether the code expresses those same meanings, because screen readers use the accessibility tree the browser builds from it, not the look. You will follow the browser’s interpretation, build an outline of headings and choose native controls that work without a mouse.
- 2ExplanationFind the role, name and state
The browser derives an accessibility tree from the HTML document. A control’s role says what it is, its name identifies it and its state says what condition it is in.
A checked "Email offers" checkbox needs all three. Screen readers use this information for speech or braille; visual styling alone cannot reliably supply it.
- 3ExampleRepair a Save control
The page uses a clickable
div. It looks like a button, but has no native button role, Tab stop or Enter/Space activation.Replace it with:
<button type="button">Save</button>The text supplies the name and the element supplies button behavior. Use Tab to reach it, then Enter and Space to check activation. Style the button with CSS to keep the intended appearance; there is no need to rebuild it as a generic container.
- 4ChallengeChoose the next check
The Save button works with a mouse. A developer says that this also proves keyboard access. What should you do?
---reveal---
Reach it with Tab and activate it with Enter and Space. Mouse activation tests only one route. A native button provides these keyboard behaviors, while a clickable generic element may still block them.
- 5ExampleGive the page an identity
Start by identifying language and page topic:
<html lang="en-US"> <head> <title>Choose a cake – Green Leaf Bakery</title> </head>The language supports pronunciation. The title distinguishes this page from other bakery tabs and is available in screen reader announcements.
Place the primary content in one visible
mainregion. Language, title and main region answer different questions: how to read, which page and where its content begins. - 6ExampleTurn section names into headings
The page contains a topic, two sections and one flavor within a section. Encode those relationships:
<h1>Choose a cake</h1> <h2>Sizes</h2> <h2>Flavors</h2> <h3>Lemon</h3>A listener can now use heading navigation to reach the right level. Read just these heading names in order: they should still describe the page’s outline. Set the visual font sizes with CSS after choosing the levels, so a smaller-looking main section can still be an
h2. - 7ChallengeKeep the outline meaningful
"Pickup details" is a main section, but
h4happens to have the desired font size. Which markup should you use?---reveal---
Use
h2for that main section and style its font size. Heading levels express relationships between sections. Choosingh4for appearance would make the outline suggest missing intermediate sections. - 8TipCheck the source route
Read the headings as a list and inspect the HTML order. Flex ordering can move a sign-in link to the start visually while leaving it last in the DOM.
Move it in the HTML if that is its meaningful position. Use real list elements for grouped links so the browser can expose both the grouping and its size.
- 9ExplanationChoose the promise of the control
"View classes" goes to a different page, so use an anchor with
href. "Show filters" changes the current page, so use a button.A link activates with Enter; a button activates with Enter or Space. Inside a form, set
type="button"on Show filters so it does not accidentally submit the form. - 10ExampleConnect opening hours to headers
Opening hours have relationships across rows and columns. Encode a small table:
<table> <caption>Opening hours</caption> <tr><th scope="col">Day</th><th scope="col">Opens</th></tr> <tr><th scope="row">Friday</th><td>08:00</td></tr> </table>The caption names the table. A person moving to 08:00 can hear the Friday row and Opens column. The header cells and scopes supply that context; arranging ordinary boxes into rows would not encode it.
- 11ChallengeReview a small page change
A new "Order now" link goes to
/order. The opening-hours table is rebuilt usingdivboxes, and its title stays a bold paragraph. What should remain native HTML?---reveal---
Keep the destination as an anchor with
href, and keep opening hours as a real table with a caption and scoped headers. Bold styling cannot supply a table caption or header-to-cell relationships. - 12RecapInspect meaning before appearance
Check the page’s role and name information, language, title, landmarks, headings and source order. Then verify links, buttons, native controls and table relationships. A screen reader outline and a short keyboard journey give two practical views of whether the structure works.
2Choose the right alternative
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionAsk what would be missing
- 2ExplanationDecide whether the photo informs
- 3ExampleName an image link by destination
- 4ExampleMake a chart available in text
- 5ChallengeDescribe a button’s function
- 6ExampleKeep one useful button name
- 7ExplanationMatch speech and sound to playback
- 8ExplanationSupply the missing visual information
- 9ChallengeComplete the media alternatives
- 10RecapChoose by purpose and medium
3Follow focus through a shopping task
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow the user’s position
- 2ExplanationUse the native keyboard route
- 3ExampleRemove invisible stops
- 4ChallengeRepair the route
- 5ExampleRestore a clear ring
- 6ExampleLook below the sticky header
- 7ChallengeMeasure a small target
- 8ExampleAdd the bypass route
- 9ChallengeTest the skip link itself
- 10ExampleOpen the native dialog
- 11TipPreserve a useful place after removal
- 12ChallengeConnect focus and visibility
- 13RecapRepeat the full interaction
4Check a readable bakery page
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionTest three kinds of readability
- 2ExplanationChoose the threshold before measuring
- 3ExampleCompare a near-threshold color
- 4ChallengeCheck two uses of one color
- 5ExplanationAdd meaning beyond hue
- 6ChallengeCheck a chart’s series
- 7ExampleFind the clipped container
- 8ExampleNarrow the effective view
- 9TipApply spacing as a user would
- 10ChallengeRepair the narrow layout
- 11ConnectionCombine the checks
- 12RecapKeep the same information available
5Help a customer finish an order
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow the whole form task
- 2ExampleConnect the email label
- 3ExampleSupply the shared question
- 4ChallengeRepair a visible-name mismatch
- 5ExplanationAdd the hint before the error
- 6ExampleSupport entry tools
- 7ChallengeCarry an answer forward
- 8ExampleIdentify a failed field
- 9ExampleGive several errors one entry point
- 10ChallengeAnnounce the cart result
- 11ConnectionCheck before committing
- 12ChallengeConnect recovery and entry effort
- 13RecapCheck labels, guidance and feedback
6Verify the visible and spoken interface
16 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionCompare what the page reports
- 2ExplanationCheck what ARIA can change
- 3ExampleTrace a surprising name
- 4ChallengePreserve two meanings
- 5ExampleMake a state agree with its panel
- 6Key InsightKeep a toggle label stable
- 7ChallengeChoose the page state
- 8ExplanationRead the scan as evidence
- 9ExampleRepair the shared source
- 10ChallengeInterpret a clean result
- 11ExampleWalk the manual route
- 12ExampleListen to names and structure
- 13ExampleWrite a reproducible finding
- 14ChallengeChoose the retest
- 15ChallengeFinish the combined review
- 16RecapVerify each kind of promise
Building Accessible Web Pages practice questions
Set 1: Choose clear structure and native controls10 Practice Questions
Question 1 of 10
Show answerHide answer
Question 2 of 10
h1 named "Bread courses" and an h2 named "Weekend courses". What heading level fits "Sunday beginners", a part of Weekend courses?Show answerHide answer
h2 section belongs at h3. The heading level expresses its place in the outline, while CSS controls its size. Choosing h4 merely because it looks smaller introduces a skipped level instead of describing the page’s structure.Question 3 of 10
/delivery page?Show answerHide answer
href represents navigation to a destination and supports normal link activation. A button represents an action, so using one here would give a different role from the destination behavior. An anchor without href does not supply an ordinary keyboard-focusable link.Question 4 of 10
Show answerHide answer
Question 5 of 10
Show answerHide answer
Question 6 of 10
opacity: 0. What should change while the menu is closed?Show answerHide answer
Question 7 of 10
Show answerHide answer
Question 8 of 10
<label for="buyer-email">Email address</label><input id="email" name="buyer-email" type="email">. What directly repairs its label connection?Show answerHide answer
Question 9 of 10
role="button", tabindex="0" and aria-label="Expand". Mouse clicks expand content, but Space does nothing and heading navigation skips it. What structure repairs heading navigation, button keys and the visible-name mismatch?Show answerHide answer
aria-label="Expand" fixes structure and keys but still tells voice users a name that does not contain the words they see.Question 10 of 10
Show answerHide answer
Set 2: Keep focus, guidance and state clear10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
display: none. Its destination is a main region with no ID. What repair gives it a usable route without adding main to every Tab sequence?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
id="pw-help". Which attribute connects that hint as a description?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
aria-invalid="false" and its aria-describedby points only to an old hint, though a new error paragraph exists. What repair gives a complete correction route?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 3: Apply the rules across real page states10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
aria-label="Return" and aria-labelledby="previous-title". The referenced heading says "Previous step". What name should the developer expect to inspect?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose barriers across an interaction10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
autocomplete="email" token. What plan addresses all three problems?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
aria-disabled="true", but clicking it still places an order. What behavior is missing if the control must remain focusable?Show answerHide answer
Save this lesson to see this answer.
Set 5: Review a page through several access routes10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
aria-hidden="true". Keyboard focus still enters those links. What repair makes the closed panel unavailable through both routes?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Accessibility inspection
Accessibility tree
Accessible description
aria-describedby while the label still supplies the name "Password".Accessible name
Alt text
ARIA state
Aria-current
aria-current="page" so the same markup controls the visible navigation highlight.Aria-disabled
Assistive technology
Audio description
Autocomplete token
autocomplete="current-password" so a password manager can recognize it.Automated accessibility check
Axe-core
B
Browser zoom
Button
C
Captions
Change of context
Color vision deficiency
Color-only cue
Complex image
Contrast ratio
Contrast threshold
D
Decorative image
alt="" so it does not interrupt the recipe when read aloud.Description list
Disclosure
aria-expanded while a script also adds or removes hidden on its list.E
Error identification
Error prevention
Error suggestion
Error summary
F
Fieldset
Focus indicator
Focus management
Focus obstruction
Focus-visible
:focus-visible for a strong keyboard ring while ordinary mouse button clicks may show none.Forced colors
Form label
Functional image
H
Header scope
scope="row" so its name is associated with that row’s opening and closing times.Heading hierarchy
h2, with "Chocolate" and "Lemon" as h3 subsections inside that section.I
Image of text
Inert region
showModal() makes the background page inert while the dialog is active.K
Keyboard focus
Keyboard trap
L
Label in Name
Landmark
Large text
Legend
Link
Live region
M
Manual accessibility test
Modal dialog
N
Native control
Non-color cue
Non-text contrast
P
Page language
lang="en-US", while a short French passage on it has its own language attribute.Page title
Placeholder
Pointer target
Presentational role
role="presentation" cannot reliably turn an active link into plain text or hide its contents.R
Redundant entry
Reflow
Relative luminance
Rem
1rem text grows when the user increases the browser’s default font size.Required field
required for browser checks.Retesting
Review alert
Role
Rotor
S
Semantic HTML
Skip link
Source order
Status message
Subtitles
SVG
role="img" and a title saying "Rated 4 out of 5 stars".T
Tab order
Tabindex
tabindex="-1" so script can focus it after the last item is removed.Table caption
Task blocker
Text-spacing override
Toggle button
Transcript
V
Viewport declaration
user-scalable=no to its viewport metadata.Visually hidden text
W
WAI-ARIA
WCAG success criterion
Web accessibility
WebVTT
- 1How the Web Works
- 2HTML
- 3HTML Forms
- 4CSS
- 5CSS Layout with Flexbox and Grid
- 6Responsive Web Design
- 7Building Accessible Web Pages
- 8Publishing a Website
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.