Accessibility is mostly HTML done properly
Most of what makes a page accessible is in the previous six lessons: real headings, real labels, real buttons, alt text, landmarks. Very little of it is an extra layer you add at the end.
The cheapest test you can run: put the mouse away. Press Tab through your page. Can you reach everything? Can you see where you are? Can you operate it?
Every control needs an accessible name
A name is the text a screen reader announces. It comes from the content of the element, or a <label>, or aria-label, or aria-labelledby — in that order of preference.
<!-- No text inside, so it has no name at all -->
<button><svg>…</svg></button>
<!-- Named, and the icon is hidden from the reader -->
<button aria-label="Close">
<svg aria-hidden="true" focusable="false">…</svg>
</button>
Use the element that already behaves
A <button> is focusable, fires on Enter and Space, and is announced as a button. A <div onclick> does none of those things, and rebuilding them correctly takes tabindex, key handlers and a role.
| You want | Use |
|---|---|
| Something that does an action | <button> |
| Something that goes somewhere | <a href> |
| A show/hide panel | <details> / <summary> |
| A modal | <dialog> with showModal() |
| A choice from a list | <select> or radios |
Do not use ARIA if a native element will do. A wrong role is worse than no role: it overrides what the browser knew and tells the reader something untrue.
Keyboard order and focus
- Tab order follows the source order. If CSS moves things visually, the tab order will no longer match what is on screen — fix the source, not the tab index.
tabindex="0"puts a non-interactive element in the tab order.tabindex="-1"makes it focusable by script only. Never use a positive number.- Never remove the focus outline without replacing it.
:focus-visiblelets you style focus for keyboard users without a ring appearing on every mouse click.
:focus-visible {
outline: 2px solid #4c8dff;
outline-offset: 3px;
}
A skip link
The first focusable thing on a page should let someone jump past the navigation. It can be visually hidden until it is focused.
<a class="skip" href="#main">Skip to content</a>
…
<main id="main" tabindex="-1">…</main>
Images, colour and text
- Alt text on every image: the message, or
alt=""if it is decoration. - Never use colour alone to carry meaning — add a word, an icon or a pattern.
- Body text needs a contrast ratio of at least 4.5:1 against its background; large text 3:1.
- Do not disable zoom.
user-scalable=noin the viewport meta is a barrier.
When you do need ARIA
aria-expanded tells the reader whether a disclosure is open. aria-live announces content that appeared without a navigation. aria-hidden="true" hides decoration — never put it on anything focusable.
<button aria-expanded="false" aria-controls="panel">Details</button>
<div id="panel" hidden>…</div>
<!-- announce changes that happen without a page load -->
<p role="status" aria-live="polite">Saved.</p>
Reduced motion
Some people get motion sickness from large movement. Respect the system setting.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}