← Component Library
Accessibility Components

Accessibility Patterns

Controls that let people tailor a page to their needs. Each with a live demo and the HTML & CSS.

01 · Font Size Controls

Let readers resize the text

A small A– / A / A+ control that scales body text up or down — helpful for low-vision readers. Try the buttons below.

Adjust the text size with the buttons above. Good controls change the body text without breaking the layout.

HTML

<div class="text-size" role="group" aria-label="Text size">
  <button data-size="15">A–</button>
  <button data-size="18">A</button>
  <button data-size="22">A+</button>
</div>
<p id="content">Adjust the text size with the buttons above. Good controls change the body text without breaking the layout.</p>

CSS

.text-size button {
  border: 1px solid #cbd5e1; background: #fff;
  border-radius: 8px; padding: 6px 12px; cursor: pointer;
}
.text-size button[aria-pressed="true"] { background: #38bdf8; color: #fff; }

JavaScript

const buttons = document.querySelectorAll('.text-size button');
const content = document.getElementById('content');
buttons.forEach(btn => btn.addEventListener('click', () => {
  content.style.fontSize = btn.dataset.size + 'px';
  buttons.forEach(b => b.setAttribute('aria-pressed', b === btn));
}));
Tip: set sizes in rem and scale the root <html> font-size so the whole page grows proportionally — and remember the browser's own zoom already helps, so this is a bonus, not a replacement.
02 · Skip Navigation Link

Jump straight to the content

A hidden link that appears only on keyboard focus, letting people skip past the menu to the main content. Click the box below, then press Tab — the blue link appears.

Skip to main content

Focus here and press Tab — a “Skip to main content” link slides in at the top.

↓ This is the main content it jumps to.

HTML — first element in the body

<a class="skip-link" href="#main">Skip to main content</a>
<nav>
  <p>Focus here and press Tab — a “Skip to main content” link slides in at the top.</p>
  … long menu …
</nav>
<main id="main">↓ This is the main content it jumps to.</main>

CSS — hidden until focused

.skip-link { position: absolute; top: -44px; left: 12px;   /* off-screen */
  background: #38bdf8; color: #fff; padding: 8px 14px; border-radius: 8px; }
.skip-link:focus { top: 12px; }                            /* slides in on Tab */
03 · Accessibility Toolbar

One bar, several adjustments

A toolbar grouping common controls — larger text, high contrast. Toggle the buttons and watch the sample change.

Sample content. Toggle the options above to see larger text and a high-contrast theme apply here.

HTML

<div class="toolbar" role="group" aria-label="Accessibility options">
  <button id="bigBtn" aria-pressed="false">Larger text</button>
  <button id="contrastBtn" aria-pressed="false">High contrast</button>
</div>
<div class="stage" id="stage">Sample content. Toggle the options above to see larger text and a high-contrast theme apply here.</div>

CSS

.toolbar { display: flex; gap: 8px; flex-wrap: wrap; background: #1f2433; padding: 10px; border-radius: 10px; }
.toolbar button { background: #2d3344; color: #fff; border: 1px solid #3d4456; border-radius: 8px; padding: 8px 12px; cursor: pointer; }
.toolbar button[aria-pressed="true"] { background: #38bdf8; border-color: #38bdf8; }
.stage { background: #fff; border: 1px solid #e5e7eb; border-radius: 10px; padding: 16px; color: #374151; margin-top: 10px; transition: all .15s; }
.stage.big { font-size: 1.2rem; }
.stage.contrast { background: #000; color: #fff; }

JavaScript

const stage = document.getElementById('stage');
const bigBtn = document.getElementById('bigBtn');
const contrastBtn = document.getElementById('contrastBtn');

// Toggle a class on the stage and keep aria-pressed in sync
function wireToggle(btn, cls) {
  btn.addEventListener('click', () => {
    const on = stage.classList.toggle(cls);
    btn.setAttribute('aria-pressed', on);   // keep state accessible
  });
}
wireToggle(bigBtn, 'big');
wireToggle(contrastBtn, 'contrast');
04 · Keyboard Navigation

Everything reachable with Tab

Many people navigate with the keyboard only. Press Tab to move between these links and Shift+Tab to go back — each shows a clear focus ring.

CSS & tips

a:focus-visible { outline: 3px solid #38bdf8; outline-offset: 2px; }

/* Use real <a> and <button> — they're keyboard-focusable for free.
   Keep the DOM order logical; avoid positive tabindex values. */
Test it yourself: put your mouse down and try to use a page with Tab, Enter, and arrow keys only. If you get stuck, so will keyboard users.
05 · Focus Indicators

Make the focused element obvious

A focus indicator shows which control is active. Tab onto both buttons — the first has its outline removed (bad), the second shows a strong ring (good).

outline: none — avoid visible outline — good

CSS

/* Never do this with nothing to replace it: */
button { outline: none; }            /* ✗ removes the only cue */

/* Do this instead — styled, high-contrast, keyboard-only: */
button:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
06 · Screen Reader Labels

Give icon controls a name

An icon-only button looks fine but reads as “button” to a screen reader. A label — via aria-label or visually-hidden text — tells people what it does.

HTML — two ways to label

<button aria-label="Search">🔍</button>

<button aria-label="Add to favorites">❤</button>

<button>🔔<span class="visually-hidden">Notifications</span></button>

CSS — the visually-hidden helper

.visually-hidden {            /* seen by screen readers, not by eyes */
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
07 · Accessible Tables

Headers a screen reader can follow

Real data tables need a <caption>, column headers in <th scope="col">, and row headers in <th scope="row"> so each cell is announced with its context.

Weekly study hours
WeekHTMLCSS
Week 132
Week 224

HTML

<table>
  <caption>Weekly study hours</caption>
  <thead><tr><th scope="col">Week</th><th scope="col">HTML</th><th scope="col">CSS</th></tr></thead>
  <tbody>
    <tr><th scope="row">Week 1</th><td>3</td><td>2</td></tr>
    <tr><th scope="row">Week 2</th><td>2</td><td>4</td></tr>
  </tbody>
</table>
Full table styling and layout lives on the Tables tutorial.
08 · Accessible Forms

Every field needs a real label

Connect each <label> to its input with for/id, describe hints with aria-describedby, and mark errors with aria-invalid. Crucially, an error must never rely on red colour alone — pair it with an icon, the word “Error,” and a thicker border so colour-blind and low-vision users get the message too.

We'll only use this to send your certificate.

Error: Must be at least 8 characters.

HTML — an error with three non-colour cues

<label for="email">Email address</label>
<input id="email" type="email" aria-describedby="emailHint">
<p id="emailHint" class="hint">We'll only use this to send your certificate.</p>

<label for="pw">Password</label>
<input id="pw" type="password" aria-invalid="true" aria-describedby="err">
<p id="err" class="error">
  <span aria-hidden="true">⚠</span>   <!-- 1. icon -->
  <strong>Error:</strong>                 <!-- 2. the word -->
  Must be at least 8 characters.
</p>

CSS — colour is the bonus, not the message

.error { color: #b91c1c; display: flex; gap: 6px; align-items: center; }
input[aria-invalid="true"] {            /* 3. thicker, tinted border */
  border: 2px solid #b91c1c; background: #fff5f5;
}
.hint { font-size: .76rem; color: #6b7280; margin-top: 3px; }
Why this matters: about 1 in 12 men has some colour-vision deficiency, and screen-reader users hear no colour at all. The icon, the word “Error,” and aria-invalid all carry the meaning without it. More patterns on the Forms tutorial.
09 · Contrast Controls

Enough contrast to read comfortably

Text needs enough contrast against its background. WCAG AA asks for a ratio of at least 4.5:1 for normal text. A contrast control lets users boost it. Toggle the switch to see the sample jump from low to high contrast.

This paragraph starts at low contrast (hard to read for many people) and switches to a strong, high-contrast theme when you turn the control on.

Light grey on white — 1.6:1 Fails AA
White on near-black — 18:1 Passes AA

HTML

<label><input type="checkbox" id="contrastToggle"> Boost contrast</label>
<p class="sample" id="sample">This paragraph starts at low contrast (hard to read for many people) and switches to a strong, high-contrast theme when you turn the control on.</p>

CSS

.sample        { background: #fff;    color: #6b7280; padding: 16px 18px; border-radius: 10px; transition: all .15s; }   /* low contrast */
.sample.boost  { background: #0b0c10; color: #fff;    }                                                                /* high contrast */

JavaScript

const contrastToggle = document.getElementById('contrastToggle');
const sample = document.getElementById('sample');
contrastToggle.addEventListener('change', () => {
  sample.classList.toggle('boost', contrastToggle.checked);  // swap to high-contrast
});
Check before you ship: a contrast checker (or your browser's DevTools) gives the exact ratio for any text/background pair. For picking accessible colours in the first place, see the Accessible Color Wheel.
10 · Related

Keep building

See the full Accessibility tutorial, the Accessible Color Wheel, and Dark / Light Mode — or browse the Component Library.