Reference guide · html-css-js · Published 2026-08-15 · 4 min read
Accessibility basics: what HTML, CSS and JS can fix
Website accessibility basics for HTML CSS and JS: contrast 4.5, keyboard navigation, focus visible, reduced motion, headings.
- ·WCAG AA basics
- ·Contrast and zoom
- ·Keyboard and motion
The WCAG Level A and AA checklist for code
WCAG has three levels; the practical target is AA. The checklist below mixes the common Level 1 (A) and Level 2 (AA) items a developer can fix with code. The parts a developer controls with code, without a redesign, set the headline goals.
- Text contrast at least 4.5:1 against its background (large text at 3:1).
- Everything operable by keyboard alone, no click-only path.
- Visible focus indicator on every focusable element.
- No content that flashes more than three times a second.
- No full-page timeouts that rely on a session timer without warning.
- Language set on the page and on text of a different language.
- Logical heading order and reading sequence.
None of these need a plugin, and the first five are CSS and JS settings. This article assumes the HTML side (headings, landmarks, labels) is already covered by the semantic HTML and accessible forms references. What remains is code labour.
Contrast: the CSS fix most sites miss
Text at default weight below about 24px needs a 4.5:1 ratio. The two classic failures: gray text on white, and white text on a photo. The remedy is not "make the text darker" by eye, it is to measure with a contrast checker, then adjust the hex, then keep the ratio when the theme switches to dark mode, because the color system flips the pair.
/* Text on the base background */
body { color: #22334a; background: #ffffff; }
/* Any pair that passes AA on the palette gets used */
.hint { color: #5c6b7a; } /* check against panel background */
The accent color is the trap: brand colors often sit near the 4.5 line or under it, so body text and icons do not reach the ratio. Reserve hero gradient backgrounds for large text or solid panels.
Keyboard and zoom: the JS-free fixes
Every interactive element must be truly reachable by Tab and Enter or Space. That means real buttons and inputs, and links for navigation that a script does not hijack. The browser gives <button> the behaviour for free; you only pay when you rebuild navigation with a <div> and try to bolt a role on later.
- Do not remove outline globally.
outline: nonewithout a replacement makes keyboard users invisible. - Use
:focus-visibleto keep the outline for keyboard and drop it for mouse only. - Allow text to resize to 200 percent without loss of function: no fixed-height cards, no
max-width(or nothing) on the root that cuts horizontal scrolling, notext-overflow: ellipsison every label. - Test zoom at 200 percent and narrow fold to 320px width on the main routes.
Reduced motion and animation
The prefers-reduced-motion media query is a profile, not a breadth test. Apply it to autoplaying carousels, scroll jacks, parallax and growth bounces, and ship a still state that reads correctly:
@media (prefers-reduced-motion: reduce) {
.carousel { scroll-behavior: auto; }
.reveal { animation: none; }
}
If a non-essential animation cannot be disabled, the animation must pause when it is not visible, which is itself an infinite animation with no pause; disable that too in the reduced query.
JavaScript that helps
JavaScript does the accessibility work in two places: live announcements and focus management.
status.setAttribute("aria-live", "polite");
status.textContent = "3 results loaded";
aria-live="polite" preserves work for a screen reader's later turn; role="alert" interrupts it. Use them for search results, cart changes, and async load completion, never for every keystroke "saving" spam.
For focus management: when a dialog opens, move focus into it and trap it; when it closes, return focus to the button that opened it. With inert you can also mark the rest of the page inert so Tab cannot wander.
The audit that powers all of it
Run the automated scan over the staging preview occasionally: it finds the missing alt, the unlabeled button, the low-contrast pair. It misses the judgement calls: whether a thing is an actual button, which the CSS versus inline styles comparison touches on, and what a dialog means for interaction, which a keyboard-only test session with a real user catches better than any scan.
Prevention
The one permanent habit: a 4.5 ratio, a focus indicator, keyboard completeness, and a reduced motion response are release criteria for every feature, not an annual tidy-up. Each release adds a color or a control somewhere that a keyboard user waits on; the feature either ships the four things or it does not ship.