UI pattern playground

Accessibility UI patterns playground

Hands-on UI pattern practice for product managers, designers, and developers. Each pattern explains who is affected and what to fix, then lets you compare broken and accessible UI. Developers also get reference code and a live editor to copy into projects. Explore Dev Tools (wcag-kit, Claude Skill, and WCAG Lens) to ask about these patterns without leaving your IDE.

Find and browse UI patterns

Search patterns by name (e.g. "modal", "breadcrumb").

All 27 patterns

Pick a pattern to jump straight to its broken and accessible demos. Use Jump to pattern or the sidebar to go directly to a section.

Forms & input

Overlays & feedback

Navigation & layout

Content & structure

Controls & interaction

Form Validation

Form validation tells users what went wrong when they submit a form. Without the right code, screen reader users submit a form and hear nothing: they have no idea an error occurred.

WCAG 1.3.1 Info and Relationships (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People with colour blindness (if errors use colour only)

Designer note

Never use colour alone to show an error. Always pair it with an icon and text. Every error message must appear next to the field it describes, not just at the top of the page.

What breaks

  • Label and input are not linked, screen readers can't associate them
  • Error uses red colour only, 8% of men have colour blindness
  • No role='alert', screen readers stay completely silent when the error appears
  • aria-invalid is missing, screen readers can't tell the field is in an error state
  • type='text' misses email-specific validation and the correct mobile keyboard

What to do instead

  • htmlFor links label to input, screen reader says 'Email Address, required, edit text'
  • Error uses a ⚠️ icon AND text, not colour alone
  • role='alert' causes the screen reader to announce the error immediately on submit
  • aria-invalid='true' tells screen readers the field contains an error
  • type='email' gives mobile users the correct keyboard and enables browser validation

Impact

Form errors are the most common barrier for screen reader users on e-commerce and sign-up flows. If an error isn't programmatically linked to its input and announced via role='alert', a blind user has no idea what went wrong or which field to fix. These ARIA attributes cost nothing to add and make forms usable for everyone.

How to test

Tab into the bad form, submit without an email, and listen, your screen reader says nothing. Do the same in the good form.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Heading Hierarchy

Headings are the invisible table of contents for a page. Screen reader users navigate by jumping between headings: if the structure is broken, they get lost or miss content entirely.

WCAG 1.3.1 Info and Relationships (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People with cognitive disabilities who rely on clear structure

Designer note

Don't choose heading levels based on how big you want the text to look. Use CSS for size. Headings should reflect the page structure: one H1, then H2 for sections, H3 for subsections.

What breaks

  • Bold or large text used instead of heading elements, missing from the heading list
  • Skipped levels (h1 then h4) break the mental model of page structure
  • Multiple h1 elements suggest several unrelated pages in one view
  • Screen reader users cannot jump by section when headings are wrong
  • Violates WCAG 1.3.1 (Info and Relationships) and 2.4.6 (Headings and Labels)

What to do instead

  • One h1 describes the main purpose of the page or view
  • h2 marks major sections; h3 marks subsections without skipping levels
  • Visual size comes from CSS, not from picking arbitrary heading levels
  • Heading rotor / landmarks list matches what sighted users see in the outline
  • Meets WCAG 1.3.1 and 2.4.6

Impact

Screen reader users often navigate by headings (H key in NVDA/JAWS, rotor in VoiceOver). A broken outline means they hear a flat wall of text or jump to the wrong section. Fixing hierarchy is mostly choosing the right HTML element, not redesigning the UI.

How to test

Open your screen reader heading list (NVDA: Insert+F7, VoiceOver: rotor → Headings). Compare the bad example’s chaotic list to the good example’s h1 → h2 → h3 tree.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Icon Button

An icon button is a clickable control with only an icon and no visible text. Without a label, a screen reader user hears only 'button' with no idea what it does.

WCAG 4.1.2 Name, Role, Value (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People with cognitive disabilities who don't recognise icons

Designer note

For every icon-only button in your design, ask: what would a screen reader announce? If the answer is just 'button', it needs a label. Consider adding a visible tooltip or text label.

What breaks

  • Variant 1: Screen reader only says 'button', zero context for the user
  • Variant 2: title is unreliable, many screen readers ignore it by default
  • Variant 3: Icon not marked aria-hidden, so it gets announced as 'image'
  • Icon-only buttons with no label are also a cognitive accessibility issue
  • All three violate WCAG 4.1.2 (Name, Role, Value)

What to do instead

  • aria-label gives the button a clear name: 'Delete item, button'
  • sr-only hides text visually while keeping it accessible to screen readers
  • aria-hidden='true' on the icon stops it being announced separately
  • All three good variants produce the same clean announcement
  • Choose the variant that fits your design, all three are correct

Impact

Icon buttons are everywhere, toolbars, tables, cards. Without a text alternative, every icon button is a mystery to a screen reader user. Adding aria-label or sr-only text takes five seconds and makes your interface usable for blind and low-vision users.

How to test

Focus each bad button and listen to your screen reader. Then focus the good buttons, notice the difference in what's announced.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Image Alt Text

Alt text is a written description of an image. When a screen reader reaches an image, it reads the alt text aloud. Without it, users hear the filename or nothing at all.

WCAG 1.1.1 Non-text Content (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People on slow connections where images don't load

Designer note

For every image in your design, ask: if this image disappeared, what information would be lost? That's what the alt text should say. Decorative images need no description.

What breaks

  • Generic alt like image or logo
  • Describes appearance instead of purpose
  • Decorative images given verbose descriptions
  • Charts missing text alternatives for data
  • Linked images missing destination context

What to do instead

  • Alt explains what would be lost if the image disappeared
  • Linked images describe the action or destination
  • Charts include data in text nearby
  • Decorative images use alt=""
  • Meets WCAG 1.1.1

Impact

Alt text replaces the image for people who cannot see it. Caption-style descriptions often miss the point the image was meant to convey.

How to test

Read the alt strings below and ask whether each one answers what information would be lost.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Color Contrast

Colour contrast is the difference in brightness between text and its background. If the contrast is too low, the text is hard or impossible to read for many users.

WCAG 1.4.3 Contrast (Minimum) (opens in a new tab)

Understand this pattern

Who is affected

  • People with low vision
  • People with colour blindness
  • Anyone in bright sunlight or on a low-quality screen

Designer note

Before finalising any colour combination for text, run it through a contrast checker. The minimum ratio is 4.5:1 for normal text and 3:1 for large text (18px bold or 24px regular).

What breaks

  • Light gray body text on white (~2.5:1) fails AA for normal text
  • Pastel success or link colours chosen for aesthetics, not readability
  • Colour used as the only way to show state (still needs sufficient contrast)
  • Placeholder and disabled styles mistaken for readable body copy
  • Violates WCAG 1.4.3 (Contrast Minimum)

What to do instead

  • Body text at roughly 4.5:1 or higher against its background (AA)
  • Links and buttons tested as foreground/background pairs, not in isolation
  • Semantic colour tokens checked in both light UI states
  • Large text (18pt+ or 14pt bold) may use 3:1 where appropriate
  • Mini checker validates hex pairs before you ship

Impact

Low contrast is one of the most common WCAG failures on the web. It affects people with low vision, users in bright sunlight, and anyone on a poor monitor. Most fixes are a darker text or background tweak, not a redesign.

How to test

Use the mini checker with your brand colours, or browser DevTools → Accessibility → Contrast. Squint at the bad examples, if the text disappears, the ratio is too low.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Accordion / Disclosure

An accordion is a list of sections that expand and collapse. Without the right code, a screen reader user cannot tell if a section is open or closed, and may not be able to open it at all.

WCAG 4.1.2 Name, Role, Value (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users
  • Blind and low-vision users
  • Screen reader users

Designer note

Every accordion trigger must look and behave like a button. Include a visual indicator (chevron or +/-) that changes when the section is open. The state change must be communicated in code, not just visually.

What breaks

  • Div trigger cannot be reached or activated by keyboard
  • No aria-expanded, screen reader cannot tell if section is open or closed
  • No aria-controls, trigger and panel are not linked
  • Panel hidden with CSS only, screen readers may still read hidden content
  • Violates WCAG 4.1.2

What to do instead

  • Button trigger is keyboard focusable and activatable with Enter or Space
  • aria-expanded updates from false to true when panel opens
  • aria-controls links the button to its panel by id
  • hidden attribute prevents screen readers from reading collapsed content
  • Meets WCAG 4.1.2

Impact

Accordions are used in FAQs, settings panels, and content-heavy pages. A keyboard user who cannot open an accordion section simply misses that content. The fix is replacing div triggers with button elements and adding two ARIA attributes.

How to test

Tab to the bad accordion trigger and press Enter: nothing happens. Tab to the good accordion trigger and press Enter: it opens. Check that your screen reader announces 'collapsed' and 'expanded' as you toggle.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Tabs

Tabs are a row of buttons that each reveal a different panel of content. Without the right code, a screen reader user hears a confusing list with no context, and keyboard users can't navigate between tabs.

WCAG 2.1.1 Keyboard (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users
  • Blind and low-vision users
  • Screen reader users

Designer note

The active tab must be distinguishable by more than colour. Include an underline, border, or shape change. Make sure there is enough visual difference between the selected and unselected states for low-vision users.

What breaks

  • Div triggers are not keyboard focusable
  • No role=tablist, role=tab, or role=tabpanel, screen readers get no context
  • No aria-selected, active tab is not announced
  • Arrow keys do nothing, keyboard users cannot move between tabs
  • Violates WCAG 2.1.1

What to do instead

  • role=tablist wraps the tab row, role=tab on each trigger
  • aria-selected=true on active tab, false on others
  • Arrow Left and Right move focus between tabs
  • Tab key enters the active panel content
  • Meets WCAG 2.1.1 and 4.1.2

Impact

Tabs are one of the most common interactive patterns on the web. Broken tab implementations leave keyboard users stuck on the first tab, unable to reach other content. The ARIA pattern is well-documented and straightforward to implement.

How to test

Focus the bad tabs and press Arrow Right: nothing moves. Focus the good tabs and press Arrow Right: focus moves to the next tab. Press Tab to enter the panel content.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Toast / Notifications

A toast is a small notification that appears briefly on screen. Without the right code, it appears visually but is completely silent for screen reader users: they never know it happened.

WCAG 4.1.3 Status Messages (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People with cognitive disabilities who rely on confirmation messages

Designer note

Every toast or notification in your design must have a text label, not just an icon or colour. Design a dismiss button for toasts that don't auto-dismiss. For auto-dismissing toasts, keep them visible for at least 5 seconds.

What breaks

  • No aria-live region, screen readers never announce the message
  • No keyboard-accessible dismiss button
  • Auto-dismisses with no pause option
  • Colour and icon used without text label
  • Violates WCAG 4.1.3

What to do instead

  • aria-live=polite region announces the message to screen readers without stealing focus
  • role=status communicates this is a non-urgent update
  • Keyboard-focusable close button with aria-label
  • Auto-dismiss pauses on hover and focus
  • Meets WCAG 4.1.3

Impact

Toasts confirm actions: item saved, form submitted, error occurred. If a screen reader user never hears the confirmation, they may repeat the action or assume it failed. aria-live is a one-line addition that makes these messages accessible.

How to test

Trigger the bad toast and listen with your screen reader: nothing is announced. Trigger the good toast: the message is read aloud without moving your focus.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Data Table

A data table organises information into rows and columns. Without the right markup, a screen reader user hears a long list of numbers with no idea which column they belong to.

WCAG 1.3.1 Info and Relationships (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • People with cognitive disabilities who rely on structured data

Designer note

Every column must have a clear header label. If a table has sortable columns, show sort direction in text or ARIA, not only with an arrow icon.

What breaks

  • Div-based layout, no semantic table structure
  • No column headers, screen readers read cells as a flat disconnected list
  • No caption, purpose of the table is not announced
  • Sort state shown visually only, not communicated to screen readers
  • Violates WCAG 1.3.1

What to do instead

  • Semantic table element with caption describing the data
  • th elements with scope=col link headers to their columns
  • aria-sort announces current sort direction to screen readers
  • Sort state change announced via live region
  • Meets WCAG 1.3.1

Impact

Data tables are common in dashboards, reports, and admin interfaces. Without proper headers, a screen reader user hears cell after cell with no context. Semantic HTML fixes this entirely.

How to test

Navigate the bad table with your screen reader and notice cells are read with no column name. Navigate the good table and hear each cell announced with its column header.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Focus Management

In a single-page app, clicking a nav link swaps content without a full page reload. Without the right code, a screen reader user clicks a link and hears nothing. They have no idea the content changed.

WCAG 2.4.3 Focus Order (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users
  • Screen reader users
  • Keyboard-only users

Designer note

Add this to acceptance criteria for SPAs: every route change moves focus to the new content and announces the page title. This is a developer task, but PMs and designers should require it.

What breaks

  • Focus stays on the nav link after content changes
  • No announcement that new content has loaded
  • Screen reader user must manually navigate to find new content
  • New page title not announced
  • Violates WCAG 2.4.3

What to do instead

  • Focus moves to the top of the new content on route change
  • New page title announced via aria-live region
  • Focus target uses tabIndex=-1 for programmatic focus
  • Screen reader user knows immediately that navigation occurred
  • Meets WCAG 2.4.3

Impact

React, Vue, and Angular apps swap content without full page reloads. This is invisible to sighted users but breaks the experience for screen reader users completely. A few lines of focus management code fix this.

How to test

Click a nav item in the bad example with your screen reader on and notice nothing is announced. Click in the good example: the new page title is announced and focus moves to the new content.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Combobox (List Autocomplete)

A combobox is a text field that shows suggestions as you type. Without ARIA, the suggestion list is invisible to screen readers and unreachable by keyboard.

WCAG 2.1.1 Keyboard (opens in a new tab)

Understand this pattern

Who is affected

  • Blind users relying on screen readers to navigate suggestions
  • Keyboard-only users who cannot click options with a mouse
  • People with motor disabilities using switch access or voice control
  • Users with cognitive disabilities who need clear state announcements

Designer note

Link the input and suggestion list in code. Use a visible label, not only a placeholder. Highlight the active suggestion visually and in ARIA.

What breaks

  • Input has no role - treated as a plain text field by screen readers
  • No aria-expanded - screen readers cannot detect the list opening
  • Options are divs - keyboard cannot reach them at all
  • No aria-activedescendant - active option is never announced
  • No label - screen readers only read the placeholder, which disappears

What to do instead

  • role="combobox" on the input - screen readers know the pattern
  • aria-expanded updates live - list open/close state is announced
  • role="listbox" and role="option" - list is fully keyboard navigable
  • aria-activedescendant tracks the focused option - announced on each move
  • Explicit label - the field is always named even after typing begins

Impact

Comboboxes appear in search fields, address forms, and tag inputs across almost every web application. Without ARIA, blind and keyboard users cannot access suggestions.

How to test

Tab to the input and type a letter. Press Arrow Down - the first option should be announced. Press Enter - the input fills and the list closes. Press Escape - the list closes without selecting. Listen for aria-expanded when the list opens and closes.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Tooltip

A tooltip explains what a control does. If it only appears on hover, keyboard and touch users never see it. If it cannot be dismissed with Escape, it may block other content. WCAG 1.4.13 requires tooltips to be hoverable, dismissible, and persistent.

WCAG 1.4.13 Content on Hover or Focus (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users who cannot hover with a mouse
  • Touch screen users who have no hover state
  • Low-vision users who zoom in and may trigger tooltips accidentally
  • Screen reader users who need the tooltip text linked via aria-describedby
  • Users with cognitive disabilities who need time to read tooltip content

Designer note

Every tooltip must be reachable without a mouse. Never put essential information only in a tooltip - supplement a visible label or aria-label, not replace it.

What breaks

  • Tooltip appears on hover only - keyboard and touch users never see it
  • No role="tooltip" - screen readers do not announce it as a tooltip
  • No aria-describedby - tooltip text is not associated with the button
  • Cannot be dismissed with Escape - may overlap other content
  • Closes when pointer leaves the button - cannot be read by hovering over it

What to do instead

  • Tooltip appears on both mouseenter and focus - reachable without a mouse
  • role="tooltip" and aria-describedby - announced as supplementary description
  • Escape dismisses the tooltip while keeping focus on the button
  • Pointer can move onto the tooltip without it closing (hoverable)
  • Tooltip persists until Escape or focus leaves - enough time to read it

Impact

Tooltips on icon buttons and truncated text are everywhere. Hover-only tooltips hide supplementary information from keyboard and touch users. WCAG 1.4.13's hoverable, dismissible, and persistent requirements are easy to miss.

How to test

Tab to a button - tooltip appears on focus. Press Escape - tooltip closes, focus stays. Hover the button, move onto the tooltip - it stays open. Move away - it closes. Confirm aria-label (name) and aria-describedby (description) on the button.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Date Picker

A date picker opens a calendar to choose a date. If days are divs, keyboard users cannot reach them. Screen readers hear numbers without month or year context.

WCAG 2.1.1 Keyboard (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users who cannot click calendar days with a mouse
  • Blind and low-vision users who need readable date announcements
  • People with motor disabilities using switch access or voice control
  • Screen reader users who need role="grid" to understand the structure
  • Mobile users who may rely on keyboard-accessible alternatives

Designer note

Show a visible focus ring on the focused day. Mark the selected date with more than colour alone. Label Prev/Next month controls clearly.

What breaks

  • Calendar days are divs - Tab cannot reach any day
  • No arrow key navigation - keyboard users are completely stuck
  • Day cells have no aria-label - screen readers announce only numbers
  • No role="grid" - the calendar structure is invisible to screen readers
  • Escape does not close the calendar - no keyboard exit

What to do instead

  • role="grid" with role="gridcell" and role="columnheader" - structure announced
  • Each day button has a full aria-label - screen readers read the complete date
  • Arrow keys navigate days in all four directions - full keyboard access
  • Escape closes the calendar and returns focus to the trigger
  • aria-pressed marks the selected date - announced by screen readers

Impact

Date pickers appear on booking and scheduling flows. An inaccessible calendar blocks keyboard and screen reader users from choosing a date unless a plain labelled text field is offered instead.

How to test

Tab to the calendar trigger and press Enter. Arrow Right moves one day forward; Arrow Down moves one week. Press Enter to select - the input updates and the calendar closes. Open again and press Escape - focus returns to the trigger.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Progress Indicator

A progress indicator shows which step you are on in a form. If step changes are visual only, screen reader users do not know they moved forward or how much is left.

WCAG 4.1.3 Status Messages (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users who rely on screen reader announcements
  • Screen reader users completing multi-step flows
  • People with cognitive disabilities who need clear step context
  • Keyboard users who need programmatic progress state

Designer note

Mark the current step with more than colour - use aria-current and a visible border or weight change. Pair a progressbar with a polite live region that announces each step.

What breaks

  • Progress bar is a styled div - no role, valuemin, valuemax, or valuenow
  • Current step shown by colour only - fails use of colour criterion
  • No aria-live region - step changes are silent
  • Step count is visual text only - not exposed to assistive tech
  • Violates WCAG 4.1.3 Status Messages

What to do instead

  • role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax
  • aria-live polite status region announces each step change
  • aria-current="step" on the active step in the list
  • Active step uses border and weight - not colour alone
  • Submit on the final step with a clear success announcement

Impact

Checkout, onboarding, and account creation flows use step indicators. Silent step changes leave screen reader users unsure whether Next worked or which fields to complete next.

How to test

Press Next on the good demo and listen: Step 2 of 3 should be announced. Inspect the progressbar - aria-valuenow should match the step. Press Submit on step 3 and confirm the success message is announced.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Pagination

Pagination lets users move between pages of search results. If controls are divs, keyboard users cannot reach them. If the current page is colour only, many users cannot tell which page is active.

WCAG 2.4.1 Bypass Blocks (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users who cannot click div page numbers
  • Blind and low-vision users who need page context in control names
  • Screen reader users who need to hear when results change
  • People with low vision who cannot rely on colour alone for the current page

Designer note

Use a nav landmark with an accessible name. Label Previous and Next - do not rely on arrow symbols alone. Mark the current page with aria-current and a visible border or weight change.

What breaks

  • Pagination is a plain div - no navigation landmark
  • Page controls are divs - Tab cannot reach them
  • Current page shown by colour only - no aria-current
  • Previous and Next are symbols only - no accessible name
  • No announcement when the page changes

What to do instead

  • nav with aria-label="Search results pagination" - landmark and context
  • Real buttons for Previous, Next, and each page number
  • aria-current="page" on the active page button
  • Active page uses border and weight - not colour alone
  • aria-live region announces Page X of 7; results region updates politely

Impact

Search and catalogue pages depend on pagination. Inaccessible controls block keyboard users from reaching other results pages, and silent page changes leave screen reader users on stale content.

How to test

Tab through the good demo - every control should be reachable. On page 1, Previous should be disabled. Press Page 3 - listen for Page 3 of 7 and confirm results update. Inspect aria-current on the active page button.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Drag and Drop

Kanban boards and sortable lists often rely on dragging. If there is no other way to move items, keyboard and voice users cannot use the feature at all.

WCAG 2.5.7 Dragging Movements (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users who cannot drag cards
  • Screen reader users who need labelled columns and move feedback
  • People using voice control or switch access
  • Users with motor disabilities who cannot perform precise drag gestures

Designer note

Keep drag for mouse users, but add a Move menu (or similar) on every draggable item. Label columns and announce when items change position.

What breaks

  • Dragging is the only way to move cards - no keyboard alternative
  • Columns have no role or label - no board context for screen readers
  • Cards lack position labels - no “in To Do” context
  • aria-grabbed / aria-dropeffect without a working AT path - historical dead ends
  • No announcement when a card moves

What to do instead

  • role="region" aria-label="Task board" - clear landmark
  • Column groups with dynamic labels like "To Do - 2 tasks"
  • Move button opens a keyboard menu - same outcome as drag
  • Escape closes the menu without moving the card
  • Screen reader hears “Write tests moved to In Progress”; focus returns to Move

Impact

Task boards and file uploads often assume dragging. Teams that ship drag-only UIs exclude keyboard users and fail WCAG 2.5.7 unless an equivalent control exists.

How to test

Tab to a Move button, press Enter, choose a destination, and confirm the card moves with an announcement. Press Escape with the menu open - the card should stay put. Drag with a mouse on the good demo - both paths should work.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Radio Group & Checkbox Group

Size and topping choices are common on order forms. If options are only styled divs with naked inputs, screen reader users hear “radio button” with no context about the group or the choice.

WCAG 1.3.1 Info and Relationships (opens in a new tab)

Understand this pattern

Who is affected

  • Blind and low-vision users who rely on programmatic labels
  • Screen reader users who need fieldset/legend for group context
  • Keyboard users who need visible focus on each native input
  • People with cognitive disabilities who benefit from clear group headings

Designer note

Use a visible legend for every radio or checkbox group. Pair each input with a label - do not rely on colour-filled custom boxes alone.

What breaks

  • Group titles are plain paragraphs - not tied to inputs
  • No fieldset or legend - group context is missing
  • No label elements - only adjacent text beside inputs
  • Checkbox visuals can change while the input stays unchecked
  • Screen readers announce generic “radio button” with no option name

What to do instead

  • fieldset and legend name each group - “Choose your size”, “Add toppings”
  • Every input has a label via htmlFor - option names are announced
  • Native radio and checkbox inputs - arrow keys and Space work as expected
  • Visible focus rings on each control
  • ScreenReaderOutput confirms size changes and topping toggles

Impact

Order and survey forms often ship custom-styled controls without labels. Users cannot tell which size or toppings are selected without seeing the screen.

How to test

Tab into the good demo’s radio group - the legend should be announced. Press Arrow Down between radios. Tab to checkboxes and press Space - listen for checked/unchecked. Confirm the bad demo announces no option names.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Infinite Scroll / Load More

Article feeds often load more rows when you scroll. If new items appear silently and focus never moves, blind and keyboard users miss content with no way to trigger or discover it.

WCAG 4.1.3 Status Messages (opens in a new tab)

Understand this pattern

Who is affected

  • Blind users who need aria-live announcements when lists grow
  • Keyboard users who cannot trigger scroll-only loading
  • Screen reader users who lose their place when DOM updates are unannounced
  • People with motor disabilities who need a visible loading state and button target

Designer note

Prefer an explicit Load more button over scroll-only fetching. Announce loading, success counts, and end-of-list states. Keep focus predictable - do not yank focus to new rows automatically.

What breaks

  • More items load on scroll with no status message - screen readers stay silent
  • No Load more button - keyboard users cannot load intentionally
  • Focus stays put while the DOM grows - no path to new articles
  • No loading indicator - users cannot tell a fetch is in progress
  • End of list is never announced when everything is loaded

What to do instead

  • section aria-label="Articles" with a semantic ul/li list
  • aria-live="polite" on the list plus a dedicated status region
  • Load more button with aria-label - disabled with "Loading..." while busy
  • aria-busy="true" on the list and spinner while fetching
  • Announces counts ("4 more articles loaded…") and "All 12 articles loaded." at the end

Impact

Infinite scroll is common on news, social, and catalog pages. Silent appends strand keyboard users and hide new content from assistive technology unless you offer an accessible alternative.

How to test

Tab to Load more in the good demo and press Enter - hear "Loading more articles", then the loaded count. Confirm focus remains on the button. Tab through new article links. Load until the button is replaced and "All 12 articles loaded." is announced. In the bad demo, scroll the list and note the silence.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

File Upload

Profile photo uploads often rely on drag-and-drop alone. If the drop zone is a plain div and feedback is visual only, keyboard and screen reader users cannot upload or hear whether it worked.

WCAG 4.1.2 Name, Role, Value (opens in a new tab)

Understand this pattern

Who is affected

  • Keyboard-only users who cannot reach drag-only drop zones
  • Blind users who need live region announcements for file selection and errors
  • Screen reader users who need the control named as a button or labelled input
  • People using voice control who rely on accessible names for upload targets

Designer note

Use a label-wrapped file input or an explicit button that opens the picker. Never rely on display:none for the only path to upload. Announce file names, progress, success, and errors.

What breaks

  • Drop zone is a div - not in the tab order and not announced as interactive
  • File input is hidden with no associated label via htmlFor
  • Drag-and-drop only - no keyboard equivalent to open the picker
  • Success and error states are colour/text only - no live region
  • Selected file name is visual only - screen readers are not told

What to do instead

  • Label wraps sr-only input - entire zone is clickable and named
  • Drag-and-drop on the drop zone with keyboard access via the file input
  • aria-live="polite" announces selection, success, and error messages
  • accept="image/*" with clear aria-label on the file input

Impact

Forms for jobs, healthcare, and account settings routinely require document or photo uploads. Inaccessible upload widgets block completion or leave users unsure whether a file was accepted.

How to test

Tab to the good drop zone - confirm a visible focus ring. Press Enter and choose an image - hear the file name in the live region. Upload and listen for success; use Simulate error to hear the failure message. In the bad demo, Tab past the zone and note silent feedback.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Skeleton / Loading State

Profile cards often show grey skeleton blocks while data loads. If those blocks have no status message and the swap to real content is silent, blind users cannot tell whether the page is still loading or already updated.

WCAG 4.1.3 Status Messages (opens in a new tab)

Understand this pattern

Who is affected

  • Blind users who rely on aria-live regions for loading and completion
  • Screen reader users who need spinners and skeletons excluded or labelled
  • Keyboard users who need a named Reload control to retest async content
  • People with cognitive disabilities who benefit from explicit busy states

Designer note

Mark the loading container aria-busy and announce start and end states. Keep skeleton shapes aria-hidden. Do not leave spinners unlabelled.

What breaks

  • Skeleton blocks are plain divs - no role, label, or busy state
  • aria-busy is missing while content fetches
  • Loaded content appears with no status message
  • Spinner icon has no accessible name
  • Transition from skeleton to content is invisible to assistive technology

What to do instead

  • aria-live="polite" and aria-atomic="true" on the profile container
  • aria-busy toggles true during load and false when the profile is ready
  • Skeleton placeholders are aria-hidden - only the status text is announced
  • Announces "Loading profile, please wait." then the loaded profile summary
  • Reload button has aria-label="Reload profile" to replay the sequence

Impact

Dashboards, social feeds, and account pages use skeleton UI everywhere. Without status messages, users assume the page is broken or miss new content entirely.

How to test

Press Reload in the good demo - hear "Loading profile, please wait." Inspect aria-busy in DevTools (true, then false after ~2 seconds). Confirm skeleton shapes are aria-hidden. Hear the loaded announcement with name and role. Compare the bad demo where Reload produces silence.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

Alert Dialog

Deleting an account is irreversible. A generic dialog with focus on Delete and no warning association puts screen reader and keyboard users one keypress away from a mistake.

WCAG 2.1.2 No Keyboard Trap (opens in a new tab)

Understand this pattern

Who is affected

  • Blind users who need alertdialog urgency and aria-describedby on the warning
  • Keyboard users who must not land on the destructive button by default
  • Screen reader users who rely on clear cancel vs confirm announcements
  • People with motor disabilities who use Escape expecting cancel, not confirm

Designer note

Use alertdialog for destructive confirmations, not dialog. Point aria-describedby at the warning text. Default focus to Cancel. Trap Tab between only the dialog actions.

What breaks

  • role="dialog" instead of role="alertdialog" - urgency is not conveyed
  • aria-describedby missing - warning text is not tied to the dialog
  • Focus defaults to Delete - destructive action is the first keypress
  • No live region when the dialog opens - appearance may be silent
  • Escape triggers delete instead of cancel - keyboard trap with harm

What to do instead

  • role="alertdialog" with aria-labelledby and aria-describedby
  • aria-haspopup on trigger - relationship to the confirmation
  • Focus moves to Cancel on open - safe default for keyboard users
  • Tab cycles only between Cancel and the labelled confirm button
  • Escape cancels; assertive live region announces "Account deleted." on confirm

Impact

Account deletion, billing cancellations, and data wipes use confirmation dialogs daily. Wrong roles and focus order cause irreversible errors that users cannot see coming.

How to test

Open the good demo - confirm focus is on Cancel and role="alertdialog" in DevTools. Tab between the two buttons only. Press Escape - dialog closes, focus returns, cancellation is announced. Confirm delete and hear the assertive success message. Compare the bad demo where focus starts on Delete and Escape deletes.

Try it: broken vs accessible

Compare the two versions before you look at code.

Broken example. This broken example is interactive so you can try keyboard and mouse. It is hidden from assistive technology - use the accessible example for screen reader testing.

Accessible example

Loading interactive demo…

For designers on your team

Designer on your team? Share wcaginpractice.com/learn/for-designers — visual accessibility guidelines with no code required.

Keep learning

Context beyond the code

The playground shows how UI patterns break and how to fix them. The Learn section explains who benefits, Accessibility for Designers covers visual guidelines, CPACC prep maps each pattern to study resources, and Dev Tools (wcag-kit, Claude Skill, and WCAG Lens) bring patterns into your editor.

Back to top