Skip to content

WCAG & ARIA

Interactive Accessibility Audit

Explore common accessibility issues and learn how to identify and fix them.

Accessibility Audit Visualizer

Find accessibility issues in simulated webpages - click on elements to discover problems

0 / 5
Issues Found
Click on elements to find issues
MyBlog
HomeAboutPostsContact
[Image: no alt text]
🖼

Welcome to My Blog

This subtitle text has very low contrast against the white background

Latest Posts

<h4> - skipped h2, h3

Check out our latest articles on web development.

Subscribe to newsletter
No <label> element
Issue Details
Click on elements in the simulated page to discover accessibility issues

The Web Content Accessibility Guidelines (WCAG) and Accessible Rich Internet Applications (ARIA) are the two pillars of web accessibility. WCAG provides the principles and success criteria, while ARIA provides the technical attributes to make dynamic web content accessible. This page covers both in depth.


WCAG 2.1 Principles — POUR

WCAG is organized around four principles, known by the acronym POUR:

P — Perceivable
Can users perceive the content?
(text alternatives, captions, sufficient contrast)
O — Operable
Can users operate the interface?
(keyboard accessible, no seizure triggers, navigable)
U — Understandable
Can users understand the content and interface?
(readable, predictable, input assistance)
R — Robust
Can the content be interpreted by assistive technologies?
(valid HTML, compatible with current and future tools)

Conformance Levels

LevelMeaningRequirement
AMinimum accessibilityMust be met (baseline)
AAAddresses major barriersTarget for most organizations (legal standard in many countries)
AAAHighest accessibilityAspirational (not usually required for entire sites)

Key WCAG Success Criteria

Perceivable

CriterionLevelDescription
1.1.1 Non-text ContentAAll non-text content has a text alternative (alt text, labels)
1.2.1 Audio/Video (Prerecorded)AProvide captions and audio descriptions
1.3.1 Info and RelationshipsAStructure conveyed through markup, not just visual formatting
1.3.4 OrientationAAContent works in both portrait and landscape orientations
1.4.1 Use of ColorAColor is not the only means of conveying information
1.4.3 Contrast (Minimum)AA4.5:1 for normal text, 3:1 for large text
1.4.4 Resize TextAAText can be resized up to 200 percent without loss of functionality
1.4.11 Non-text ContrastAAUI components and graphics have 3:1 contrast ratio

Operable

CriterionLevelDescription
2.1.1 KeyboardAAll functionality is available from a keyboard
2.1.2 No Keyboard TrapAKeyboard focus can move away from any component
2.2.1 Timing AdjustableAUsers can extend, adjust, or disable time limits
2.3.1 Three FlashesANo content flashes more than 3 times per second
2.4.1 Bypass BlocksASkip navigation mechanism provided
2.4.3 Focus OrderAFocus order preserves meaning and operability
2.4.6 Headings and LabelsAAHeadings and labels describe topic or purpose
2.4.7 Focus VisibleAAKeyboard focus indicator is visible
2.5.1 Pointer GesturesAMultipoint gestures have single-pointer alternatives

Understandable

CriterionLevelDescription
3.1.1 Language of PageADefault language is programmatically set
3.1.2 Language of PartsAALanguage of each passage is programmatically set
3.2.1 On FocusANo unexpected context changes on focus
3.2.2 On InputANo unexpected context changes on input
3.3.1 Error IdentificationAErrors are identified and described to the user
3.3.2 Labels or InstructionsAInput fields have labels or instructions
3.3.3 Error SuggestionAAError messages suggest corrections

Robust

CriterionLevelDescription
4.1.1 ParsingAHTML is valid and properly nested
4.1.2 Name, Role, ValueAAll UI components have accessible names and roles
4.1.3 Status MessagesAAStatus messages announced without focus change

ARIA — Accessible Rich Internet Applications

ARIA provides attributes that supplement HTML to make dynamic web content accessible. It defines roles, states, and properties.

The Five Rules of ARIA

ARIA Roles

Roles define the type of UI element:

<!-- Widget roles -->
<div role="button">Click me</div>
<div role="checkbox" aria-checked="false">Option</div>
<div role="tab">Tab 1</div>
<div role="dialog" aria-modal="true">Modal content</div>
<div role="alert">Error message</div>
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
<div role="slider" aria-valuenow="50"></div>
<div role="switch" aria-checked="true">Dark mode</div>
<!-- Document structure roles -->
<div role="heading" aria-level="2">Section Title</div>
<div role="list"><div role="listitem">Item</div></div>
<div role="table">...</div>
<div role="img" aria-label="Description">...</div>
<div role="tooltip">Helpful information</div>
<!-- Landmark roles (prefer semantic HTML elements) -->
<div role="banner"> <!-- prefer <header> -->
<div role="navigation"> <!-- prefer <nav> -->
<div role="main"> <!-- prefer <main> -->
<div role="complementary"> <!-- prefer <aside> -->
<div role="contentinfo"> <!-- prefer <footer> -->
<div role="search"> <!-- prefer <search> in modern HTML -->
<div role="form"> <!-- prefer <form> with accessible name -->
<div role="region"> <!-- prefer <section> with accessible name -->

ARIA States and Properties

States are dynamic and change with user interaction. Properties are more static.

<!-- States (change during interaction) -->
<button aria-expanded="false">Menu</button>
<input aria-invalid="true" />
<div role="checkbox" aria-checked="mixed"></div>
<button aria-pressed="true">Bold</button>
<div aria-hidden="true">Decorative content</div>
<div aria-disabled="true">Disabled section</div>
<li aria-selected="true">Selected item</li>
<div aria-busy="true">Loading...</div>
<!-- Properties (generally static) -->
<input aria-label="Search" />
<input aria-labelledby="label-id" />
<input aria-describedby="help-text-id" />
<div aria-owns="child-element-id"></div>
<div aria-controls="panel-id"></div>
<ul aria-orientation="horizontal"></ul>
<input aria-required="true" />
<input aria-autocomplete="list" />
<div aria-live="polite">Dynamic content area</div>
<div aria-roledescription="slide">Carousel slide</div>

Accessible Names

Every interactive element needs an accessible name — the text a screen reader announces:

<!-- Method 1: Visible label (preferred) -->
<label for="email">Email address</label>
<input id="email" type="email" />
<!-- Method 2: aria-label (when no visible label) -->
<button aria-label="Close dialog">×</button>
<input type="search" aria-label="Search products" />
<!-- Method 3: aria-labelledby (reference another element) -->
<h2 id="section-title">User Settings</h2>
<form aria-labelledby="section-title">...</form>
<!-- Method 4: title attribute (last resort) -->
<input type="text" title="Zip code" />
<!-- Priority order for accessible name calculation:
1. aria-labelledby
2. aria-label
3. <label> element
4. title attribute
5. placeholder (NOT recommended as sole label) -->

Landmark Regions

Landmarks provide a way for screen reader users to quickly navigate to major sections of a page:

<body>
<header> <!-- banner landmark -->
<nav aria-label="Main"> <!-- navigation landmark -->
...
</nav>
</header>
<nav aria-label="Breadcrumb"> <!-- another navigation landmark -->
...
</nav>
<main> <!-- main landmark (only one per page) -->
<section aria-labelledby="intro-heading"> <!-- region landmark -->
<h2 id="intro-heading">Introduction</h2>
...
</section>
<form aria-labelledby="search-heading"> <!-- form landmark -->
<h2 id="search-heading">Search</h2>
...
</form>
</main>
<aside> <!-- complementary landmark -->
...
</aside>
<footer> <!-- contentinfo landmark -->
<nav aria-label="Footer"> <!-- navigation landmark -->
...
</nav>
</footer>
</body>

Live Regions

Live regions announce dynamic content changes to screen readers without requiring the user to move focus.

aria-live Values

ValueBehaviorUse Case
offChanges not announcedDefault
politeAnnounced after current taskStatus updates, non-urgent messages
assertiveAnnounced immediately, interrupting current speechErrors, critical alerts

Examples

<!-- Polite: status message (announced after current speech) -->
<div aria-live="polite" aria-atomic="true">
<!-- Content dynamically updated by JavaScript -->
3 results found
</div>
<!-- Assertive: error alert (announced immediately) -->
<div role="alert">
<!-- role="alert" implies aria-live="assertive" -->
Payment failed. Please check your card details.
</div>
<!-- Status: non-critical information -->
<div role="status">
<!-- role="status" implies aria-live="polite" -->
File uploaded successfully.
</div>
<!-- Log: sequential information -->
<div role="log" aria-live="polite">
<!-- New entries added dynamically -->
<p>User joined the chat</p>
<p>Message sent</p>
</div>

Live Region Attributes

<div
aria-live="polite"
aria-atomic="true" <!-- Announce entire region, not just changed part -->
aria-relevant="additions" <!-- What types of changes to announce:
additions, removals, text, all -->
>
Updated content here
</div>

Common ARIA Patterns

Tabs

<div role="tablist" aria-label="Product information">
<button
role="tab"
id="tab-1"
aria-selected="true"
aria-controls="panel-1"
tabindex="0"
>
Description
</button>
<button
role="tab"
id="tab-2"
aria-selected="false"
aria-controls="panel-2"
tabindex="-1"
>
Reviews
</button>
<button
role="tab"
id="tab-3"
aria-selected="false"
aria-controls="panel-3"
tabindex="-1"
>
Shipping
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1" tabindex="0">
<p>Product description content...</p>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" tabindex="0" hidden>
<p>Reviews content...</p>
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3" tabindex="0" hidden>
<p>Shipping content...</p>
</div>

Keyboard interaction:

  • Tab to move to the tab list, then into the active panel
  • Arrow Left/Right to move between tabs
  • Home/End to move to first/last tab
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
>
<h2 id="dialog-title">Delete Account</h2>
<p id="dialog-desc">
Are you sure you want to delete your account?
This action cannot be undone.
</p>
<div class="dialog-actions">
<button onclick="closeDialog()">Cancel</button>
<button onclick="deleteAccount()">Delete</button>
</div>
</div>

Requirements:

  • Focus is trapped inside the dialog (Tab cycles through dialog elements)
  • Escape closes the dialog
  • Focus returns to the element that opened the dialog
  • Content behind the dialog is inert (aria-hidden="true" or inert attribute)
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="user-menu"
id="menu-button"
>
User Menu
</button>
<ul
role="menu"
id="user-menu"
aria-labelledby="menu-button"
hidden
>
<li role="menuitem"><a href="/profile">Profile</a></li>
<li role="menuitem"><a href="/settings">Settings</a></li>
<li role="separator"></li>
<li role="menuitem"><a href="/logout">Sign Out</a></li>
</ul>

Keyboard interaction:

  • Enter/Space opens the menu
  • Arrow Down/Up navigates between items
  • Escape closes the menu and returns focus to the trigger
  • Type-ahead: typing a letter jumps to the next item starting with that letter

Accordion

<div class="accordion">
<h3>
<button
aria-expanded="true"
aria-controls="section1-content"
id="section1-header"
>
Section 1
</button>
</h3>
<div
id="section1-content"
role="region"
aria-labelledby="section1-header"
>
<p>Section 1 content...</p>
</div>
<h3>
<button
aria-expanded="false"
aria-controls="section2-content"
id="section2-header"
>
Section 2
</button>
</h3>
<div
id="section2-content"
role="region"
aria-labelledby="section2-header"
hidden
>
<p>Section 2 content...</p>
</div>
</div>

When NOT to Use ARIA

ARIA should be your last resort, not your first tool. Here are common cases where native HTML is better:

Instead of ARIAUse Native HTML
<div role="button"><button>
<div role="link"><a href="...">
<div role="heading" aria-level="2"><h2>
<div role="list"><div role="listitem"><ul><li>
<div role="checkbox" aria-checked="false"><input type="checkbox">
<div role="textbox"><input type="text"> or <textarea>
<div role="navigation"><nav>
<div role="main"><main>
<div role="img" aria-label="..."><img alt="...">

Native HTML elements come with:

  • Built-in keyboard interaction
  • Built-in focus management
  • Built-in accessibility semantics
  • Built-in form validation
  • No JavaScript required