Colour, Contrast and Motion
Contrast ratios that meet the guidelines, never relying on colour alone, and honouring the reduced motion preference. Mostly CSS, entirely part of the job.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Contrast
Low contrast text is the most commonly detected accessibility failure on the web, found on a large majority of home pages surveyed each year. It is also among the easiest to fix.
The requirements
| Content | Level AA | Level AAA |
|---|---|---|
| Body text | 4.5 to 1 | 7 to 1 |
| Large text (24px, or 19px bold) | 3 to 1 | 4.5 to 1 |
| Interface components and graphics | 3 to 1 | - |
| Focus indicators | 3 to 1 | - |
| Disabled controls | No requirement | - |
The ratio is calculated from relative luminance, so it is not the same as how different two colours look. Two colours can feel very distinct and still fail.
/* fails: about 2.8 to 1 */
.muted { color: #a0aec0; background: #ffffff; }
/* passes AA: about 4.6 to 1 */
.muted { color: #6b7280; background: #ffffff; }
/* comfortably passes: about 8.6 to 1 */
.body { color: #334155; background: #ffffff; }Check with the DevTools colour picker, which shows the ratio and the pass or fail marks directly as you adjust a colour.
Where it usually fails
- Grey placeholder and helper text.
- Text over a photograph, where the contrast varies across the image.
- Brand colours used for links or buttons.
- Light grey borders on form fields.
- Dark mode themes converted by inverting colours.
/* text over an image: guarantee the contrast with a scrim */
.hero {
position: relative;
color: #ffffff;
}
.hero::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(15,23,42,0.75), rgba(15,23,42,0.25));
}
.hero > * { position: relative; }Never rely on colour alone
Around one in twelve men and one in two hundred women have some form of colour vision deficiency. Anything communicated only by colour is invisible to them - and to anyone using a monochrome display or printing the page.
<!-- colour only -->
<span class="status-red">Rejected</span>
<span class="status-green">Approved</span>
<!-- colour plus text plus a shape -->
<span class="status status-rejected">
<svg aria-hidden="true"><use href="#icon-cross"/></svg> Rejected
</span>
<span class="status status-approved">
<svg aria-hidden="true"><use href="#icon-tick"/></svg> Approved
</span>The same applies to:
- Links in body text. Underline them, or add another non colour difference.
- Form errors. A red border is not enough; add text and an icon.
- Required fields. Mark them with text or an asterisk, not colour.
- Charts. Use patterns, direct labels or different shapes as well as colour.
Motion
Animation causes real problems for people with vestibular disorders: parallax scrolling, large moving elements and zooming transitions can trigger dizziness and nausea. Operating systems carry a preference for this, and CSS can read it.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Those five lines are the single highest value accessibility snippet available. Include them in every project.
A more considered approach reduces rather than removes - a fade instead of a slide, for instance:
.panel { transition: transform 250ms, opacity 250ms; }
@media (prefers-reduced-motion: reduce) {
.panel { transition: opacity 150ms; transform: none; }
}In JavaScript:
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduce) startCarouselAutoplay();Anything that moves for more than five seconds needs a pause control
Auto playing carousels, scrolling tickers and looping background video all fall under this. A visible pause button is required, not optional.
Other preference queries
@media (prefers-color-scheme: dark) {
:root { --bg: #0f172a; --text: #e2e8f0; }
}
@media (prefers-contrast: more) {
:root { --border: #000000; --text: #000000; }
}
@media (forced-colors: active) {
/* Windows high contrast mode: the system replaces your palette */
.card { border: 1px solid; }
}In forced colours mode the operating system overrides colours entirely. Backgrounds set as images disappear, so anything that conveys meaning through a background needs a border or text as well.
Text sizing and zoom
- Pages must remain usable at 200 percent zoom. Fixed pixel heights and
overflow: hiddenare the usual causes of failure. - Text must be resizable to 200 percent without loss of content.
- Setting font sizes in
pxoverrides the browser default a reader has chosen. Userem. - Comfortable line length is around sixty to seventy five characters, which
max-width: 65chexpresses directly. - Line height of at least 1.5 for body text.
body {
font-size: 1rem;
line-height: 1.6;
}
p, li { max-width: 65ch; }Important rules
- 4.5 to 1 for body text, 3 to 1 for large text and interface components.
- Never convey information by colour alone.
- Honour
prefers-reduced-motion. - Anything moving for over five seconds needs a pause control.
- Pages must work at 200 percent zoom.
- Use
remfor text so browser settings are respected.
Common mistakes
- Light grey placeholder text well below the required ratio.
- Text over an image with no scrim.
- Removing link underlines in body text.
- Status shown only by colour.
- Parallax and large transitions with no reduced motion query.
- Auto playing carousels with no pause.
- Font sizes in pixels.
- Fixed heights that clip content at high zoom.
Best practices
- Check contrast while choosing colours, not afterwards.
- Build a palette that passes and use it as tokens.
- Always pair colour with text or a shape.
- Add the reduced motion block to every project stylesheet.
- Use
remfor type andchfor line length. - Test at 200 percent zoom and in high contrast mode.
Practice
- Check every text colour in one of your stylesheets against its background and record the ratios.
- Find a status indicator that uses colour alone and add a second signal.
- Add the reduced motion block and confirm animations stop when the system setting is on.
- Zoom a page to 200 percent and list everything that breaks.