Skip to content

Accessibility Reference

A quick lookup for web accessibility concepts grouped by category. Each entry covers what it is, when to use it, and how it works so you can build accessible experiences without leaving the platform.

ARIA roles tell assistive technologies what an element is — its purpose in the interface. Set them with the role attribute. Prefer native HTML elements over ARIA roles when possible; a native <button> is better than <div role="button">.

Landmarks let screen reader users jump directly to major sections of a page.

RoleNative equivalentWhat it marks
banner<header> (top-level)Site header — logo, primary nav
navigation<nav>A set of navigation links
main<main>Primary page content
complementary<aside>Supporting content (sidebars, callouts)
contentinfo<footer> (top-level)Footer — copyright, legal, contact
search<search>Search form or region
form<form> (with accessible name)A significant form region
region<section> (with accessible name)A generic named region

Prefer the native HTML element — it carries the role implicitly. Use an explicit role only when you cannot use the native element.

Widget roles describe interactive controls.

RoleWhat it describesNotes
buttonA clickable actionUse <button> instead
checkboxA two-state toggleUse <input type="checkbox"> instead
radioA single-select option in a groupUse <input type="radio"> instead
textboxA text entry fieldUse <input> or <textarea> instead
comboboxA combined input and listboxComplex — requires aria-expanded, aria-controls
listboxA list of selectable optionsAlternative to <select> for custom dropdowns
optionAn item in a listboxChild of listbox
tabA tab in a tab listRequires tablist parent and tabpanel
tablistA container for tab controlsParent of tab elements
tabpanelThe content panel for a tabLinked to its tab via aria-controls
dialogA modal or non-modal dialogRequires accessible name and focus management
alertdialogA dialog requiring immediate responseLike dialog but announces urgently
tooltipA contextual label shown on hover/focusLinked to trigger via aria-describedby

Live regions announce dynamic content changes to screen reader users without requiring focus.

RoleWhat it doesUse for
statusPolitely announces non-critical updatesStatus messages, save confirmations
alertUrgently interrupts to announce a messageErrors, warnings, important notices
logAnnounces new content in orderChat logs, activity feeds
progressbarCommunicates progress of a taskFile uploads, form steps
<!-- Status message (polite) -->
<div role="status" aria-live="polite">Changes saved.</div>
<!-- Alert (urgent) -->
<div role="alert">Error: Please correct the highlighted fields.</div>
RoleWhat it marks
articleSelf-contained content (blog post, card) — use <article>
listA list of items — use <ul> or <ol>
listitemAn item in a list — use <li>
headingA section heading — use <h1><h6>
imgAn image or graphic — use <img> with alt
figureA figure with optional caption — use <figure>
separatorA thematic break — use <hr>
presentationRemoves semantic meaning from element
noneSame as presentation

ARIA attributes provide additional information that HTML alone cannot express. They never change visual appearance — only what assistive technologies communicate.

AttributeWhat it doesExample
aria-labelProvides an accessible name directly as a string<button aria-label="Close dialog">×</button>
aria-labelledbyPoints to one or more elements whose text is the accessible name<input aria-labelledby="email-label">
aria-describedbyPoints to elements that provide additional description<input aria-describedby="email-hint">
aria-placeholderHint text for inputs — prefer the placeholder attribute<input aria-placeholder="mm/dd/yyyy">
aria-roledescriptionOverrides the role’s spoken nameUse sparingly — can confuse users

Prefer aria-labelledby over aria-label when a visible label already exists — it keeps the visible and accessible names in sync.

State attributes reflect the current condition of an element and update dynamically as the user interacts.

AttributeValuesWhat it communicates
aria-expandedtrue / falseWhether a disclosure (menu, accordion) is open
aria-checkedtrue / false / mixedChecked state of a checkbox or radio
aria-selectedtrue / falseWhether an option/tab is currently selected
aria-pressedtrue / false / mixedWhether a toggle button is active
aria-disabledtrue / falseWhether an element is disabled (but still in tab order)
aria-hiddentrue / falseHides element from assistive tech while keeping it visible
aria-invalidtrue / false / grammar / spellingMarks a field as having an error
aria-busytrue / falseSignals that a region is still loading
aria-currentpage / step / date / true / falseMarks the current item in a set (e.g., current nav link)
AttributeWhat it does
aria-controlsPoints to the element this control manages (e.g., a button that opens a panel)
aria-ownsDeclares a parent–child relationship not expressed in the DOM
aria-flowtoSuggests an alternative reading order
aria-activedescendantPoints to the focused child in a composite widget (e.g., a listbox)
aria-errormessagePoints to the element containing the error message for an invalid field
aria-detailsPoints to an element with extended description
AttributeValuesWhat it does
aria-liveoff / polite / assertiveControls how urgently changes are announced
aria-atomictrue / falseWhether the whole region or only changed parts are announced
aria-relevantadditions / removals / text / allWhat types of changes trigger an announcement

These HTML elements have built-in landmark roles. Use them instead of <div> to give your page meaningful structure that assistive technology users can navigate.

HTML elementImplicit ARIA roleWhen to use
<header> (top-level)bannerSite-wide header — wrap logo and primary navigation
<nav>navigationAny set of navigation links — label multiple <nav>s with aria-label
<main>mainThe primary page content — one per page
<aside>complementarySidebars, related content, callouts
<footer> (top-level)contentinfoSite-wide footer — copyright, contact, legal
<section>region (with accessible name)Named content region — add aria-labelledby or aria-label
<article>articleSelf-contained content: blog post, card, comment
<form>form (with accessible name)Significant form — add aria-label or aria-labelledby
<search>searchSearch form or region

<header> and <footer> only carry their landmark role when they are direct children of <body>. Nested inside an <article> or <section>, they are generic elements.

<body>
<header> <!-- role="banner" -->
<nav aria-label="Primary">...</nav> <!-- role="navigation" -->
</header>
<main> <!-- role="main" -->
<article>...</article> <!-- role="article" -->
<aside aria-label="Related">...</aside> <!-- role="complementary" -->
</main>
<footer> <!-- role="contentinfo" -->
<nav aria-label="Footer">...</nav>
</footer>
</body>

Always use <button> for actions. If a button only contains an icon, add aria-label to provide an accessible name.

<!-- Text button — no extra ARIA needed -->
<button type="button">Save changes</button>
<!-- Icon-only button — label required -->
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
<!-- Toggle button -->
<button type="button" aria-pressed="false" id="mute-btn">Mute</button>

Mark <svg> icons with aria-hidden="true" and focusable="false" so they are invisible to assistive tech and do not receive focus in IE/Edge.

Use <a> for navigation (changing location), <button> for actions (doing something). Avoid “click here” or “read more” — the link text should make sense out of context.

<!-- Descriptive link text -->
<a href="/courses/html/">Start the HTML Foundations course</a>
<!-- Visually hidden additional context (screen-reader only) -->
<a href="/courses/css/">
CSS Foundations
<span class="sr-only"> course overview</span>
</a>
<!-- Current page in nav -->
<a href="/courses/html/" aria-current="page">HTML Foundations</a>

Every input needs a visible <label> associated by for/id. Use aria-describedby for hints and error messages.

<div>
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
aria-describedby="email-hint email-error"
aria-invalid="false"
autocomplete="email"
>
<p id="email-hint">We'll only use this to send your confirmation.</p>
<p id="email-error" role="alert" hidden>Please enter a valid email address.</p>
</div>

When a field has an error, set aria-invalid="true" and show the role="alert" message by removing hidden.

A disclosure navigation (mobile hamburger or desktop dropdown) needs aria-expanded on the toggle and a matching aria-controls pointing to the menu.

<button
type="button"
aria-expanded="false"
aria-controls="primary-nav"
aria-label="Open menu"
>
<svg aria-hidden="true" focusable="false">...</svg>
</button>
<ul id="primary-nav" hidden>
<li><a href="/">Home</a></li>
<li><a href="/courses/">Courses</a></li>
</ul>

Toggle aria-expanded between "true" and "false" and show/hide the menu when the button is clicked.

A modal needs role="dialog", an accessible name, and careful focus management.

<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
id="confirm-dialog"
hidden
>
<h2 id="dialog-title">Confirm deletion</h2>
<p>This action cannot be undone. Are you sure?</p>
<button type="button">Delete</button>
<button type="button">Cancel</button>
</div>

When the dialog opens: move focus to the first focusable element inside it, trap Tab/Shift+Tab within it, and restore focus to the trigger when it closes. When it closes: return focus to the element that opened it.

A skip link lets keyboard users bypass repetitive navigation and jump straight to the main content. Place it as the very first element in <body>.

<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- ... navigation ... -->
<main id="main-content" tabindex="-1">
<!-- page content -->
</main>

Style .skip-link to be visually hidden until focused:

.skip-link {
position: absolute;
top: -100%;
left: 0;
}
.skip-link:focus {
top: 0;
}

ConceptRule
Visible focus indicatorNever remove outline without replacing it with an equally visible alternative
tabindex="0"Adds a non-interactive element to the natural tab order
tabindex="-1"Makes an element focusable by script (element.focus()) but removes it from tab order
tabindex > 0Avoid — creates an unpredictable tab order
element.focus()Use to move focus programmatically — required for dialogs, errors, route changes

The natural tab order follows DOM order. Elements that receive focus by default:

  • <a> with href
  • <button>
  • <input>, <select>, <textarea>
  • <details>
  • Any element with tabindex="0"

Elements that are skipped: <div>, <span>, <p>, and any element with tabindex="-1" or disabled.

Standard keyboard behaviors users expect from common widgets:

WidgetKeysBehavior
ButtonEnter, SpaceActivate
LinkEnterFollow link
CheckboxSpaceToggle
Radio groupArrow keysMove between options
SelectArrow keys, EnterNavigate and select
Modal dialogEscapeClose; restore focus to trigger
MenuArrow keysNavigate items; Escape closes
TabsArrow keysSwitch tabs; Home/End for first/last
AccordionEnter, SpaceToggle panel
// Trap focus inside a modal
dialog.addEventListener('keydown', (event) => {
if (event.key !== 'Tab') return;
const focusable = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
// Close on Escape
dialog.addEventListener('keydown', (event) => {
if (event.key === 'Escape') closeDialog();
});

To provide context for screen reader users without displaying it visually, use a utility class that removes the element from the visual layout while keeping it in the accessibility tree:

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

Do not use display: none or visibility: hidden — those hide content from assistive technologies too.