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));
}));
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.One bar, several adjustments
A toolbar grouping common controls — larger text, high contrast. Toggle the buttons and watch the sample change.
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');
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).
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; }
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;
}
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.
| Week | HTML | CSS |
|---|---|---|
| Week 1 | 3 | 2 |
| Week 2 | 2 | 4 |
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>
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.
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; }
aria-invalid all carry the meaning without it. More patterns on the Forms tutorial.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.
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
});
Keep building
See the full Accessibility tutorial, the Accessible Color Wheel, and Dark / Light Mode — or browse the Component Library.