Command Palette

Search for a command to run...

Hectal
PHASE 15Advanced ~24 min· topic 5 of 7

Topic 15.5

Contrast, Motion, Zoom and Sound

In one line

Accessibility is not only for screen readers. Check that marks have enough contrast in both themes, that the page reflows at high zoom, that motion respects the reader's setting, and consider sound as another way to hear a trend.

Think of it like this

A road sign. It is made for a driver at speed, in rain and at night, for people with weak eyes or tired ones. Big letters, high contrast, a plain shape, and no flashing. A chart is read in worse conditions than we picture: a bright office, a phone in sunlight, a wall screen from the back of the room.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

contrast ratio
How different two colours are in lightness, from 1 to 1 (the same) up to 21 to 1 (black on white).
relative luminance
How bright a colour is to the eye, from 0 (black) to 1 (white), the number the ratio is made from.
reflow
The page rearranging into one column when the window is narrow or zoomed in.
reduced motion
A system setting a person turns on to avoid animation, read with the prefers-reduced-motion query.
sonification
Showing data as sound: for example, higher values as higher pitches.
pitch
How high or low a note sounds, measured in hertz (Hz).
AudioContext
The browser object that makes sound with Web Audio.

Step by step

01Measure the contrast

The first example turns each token's hex colour into a relative luminance, then into a ratio against the card. It prints pass or FAIL against 3 (marks) or 4.5 (text).

02Read the result honestly

Accent, alert, muted text and ink pass in both themes. The grid lines fail: they are faint on purpose. Whether that is acceptable depends on whether a person needs the grid to read the chart, and the axis labels carry the numbers. Say it as a decision, not a pass.

03Check reduced motion

The second example calls the dashboard's ms() with and without reduced motion. Every duration becomes 0 with it: the same code path, no movement.

04Plan the notes

The third example maps rides to a frequency between 220 and 880 Hz, handles a gap (a rest) and equal values (one steady note), and logs the plan. It never plays: sound waits for a click.

05Reflow at 320 pixels

The measured block shows the whole dashboard at a 320 pixel window: the page is exactly 320 wide, with no horizontal scrolling.

Try it yourself

  1. 1

    Try a lighter teal

    In the first example, change the light theme's accent to #2a9d8f. The first light line becomes light accent 3.32:1 needs 3 pass: still above 3, with a lot less to spare than the dashboard's #1f7a6f at 5.16.

  2. 2

    Change the pitch range

    In the third example, change high = 880 to high = 440. Tuesday plays at 440 Hz instead of 880 Hz, and Wednesday at 389 Hz. The shape is the same, and the range is one octave instead of two.

Code & diagrams

Contrast ratios for the dashboard's colours New tab

The formula is the one WCAG gives for relative luminance and contrast ratio. The tokens are the ones from theme.css (Topic 10.4).

Console

(nothing was printed)

Expected console output

light accent   5.16:1 needs 3 pass
light alert    5.18:1 needs 3 pass
light muted    5.50:1 needs 4.5 pass
light ink     14.47:1 needs 4.5 pass
light grid     1.29:1 needs 3 FAIL
dark  accent   8.64:1 needs 3 pass
dark  alert    6.97:1 needs 3 pass
dark  muted    7.26:1 needs 4.5 pass
dark  ink     13.68:1 needs 4.5 pass
dark  grid     1.41:1 needs 3 FAIL
Orange against teal: 1.00:1. Same lightness, so a grey-scale view cannot tell Monday's bar from the rest: the words must say it too.
Reduced motion in one place New tab

The checker's page has not asked for reduced motion, so the setting reads false. The test passes true for the second line.

Console

(nothing was printed)

Expected console output

prefers-reduced-motion is a setting the visitor chose, so the code asks for it: prefersReducedMotion() is boolean
reduced = false | line reveal: 600 ms | bar move: 450 ms | stagger per bar: 10 ms
reduced = true  | line reveal: 0 ms | bar move: 0 ms | stagger per bar: 0 ms
The query exists: true
Sonification: plan the notes, play on a click New tab

The checker cannot hear, so this example only logs the planned frequencies. play() is the real code path: it runs when a person presses the button.

Console

(nothing was printed)

Expected console output

Mon  2979 -> 220 Hz
Tue  3992 -> 880 Hz
Wed  3759 -> 728 Hz
Thu  3742 -> 717 Hz
Fri  3648 -> 656 Hz
Sat  3013 -> 242 Hz
Sun  3451 -> 528 Hz
with a gap: 220, rest, 880
all equal: 550, 550
Button added; play() is defined and has not been called.
The page at a 320 pixel window (measured, not live)text

Measured with Playwright 1.63 in Chromium, viewport 320 by 800, the Phase 15 dashboard and the Phase 14 one.

document.documentElement.scrollWidth, clientWidth at 320 px:
  Phase 14 dashboard: 320, 320
  Phase 15 dashboard: 320, 320   (no horizontal scrolling)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget the all-equal case

In the third example, delete the line if (max === min) return (low + high) / 2; ... in pitch.

terminal
$ Console
── what you'll see ──
with a gap: 220, rest, 880
all equal: NaN, NaN
Button added; play() is defined and has not been called.

Myth vs fact

Myth

If the text passes 4.5 to 1, the chart is fine.

Fact

The bars, lines and focus rings have their own 3 to 1 rule, and colour cannot be the only signal.

Myth

Dark mode fixes contrast.

Fact

Dark mode changes the colours, not the check. Measure both themes: the grid failed in both.

Myth

Reduced motion means no animation at all, ever.

Fact

It means less motion where it is not essential. A fade or an instant change is fine, and one function that shortens every duration is the easy route.

Myth

Sonification replaces a description.

Fact

It is an extra way to feel a trend. Some people cannot hear it, and it does not say which day or how many.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Contrast tools compute against a flat colour. A bar over a gradient or an image needs a test at its worst point. Text over a chart (the note "App down") has a halo here (paint-order: stroke) so it holds against whatever is behind it.

  • ▸

    In Windows forced-colours mode the system replaces your colours. SVG that uses currentColor and CSS variables follows it better than SVG with fixed hex values. Check that bars still show (system colours for fill) and that nothing disappears.

  • ▸

    Web Audio needs a user gesture to start: a context made before a click starts suspended. Make a new AudioContext inside the click handler and keep the notes short, with a quick fade so they do not click.

Remember this

  1. 1

    The problem this solves: people who see still do not all see the same. Some have low vision, some have colour-vision differences, some zoom in to 200% or 400%, some feel sick from motion, some use a screen in sunlight. A chart that is only right for one pair of good eyes in a dim room is not accessible.

  2. 2

    Contrast has two rules. Text needs a ratio of at least 4.5 to 1 against its background (WCAG 1.4.3 Contrast (Minimum), level AA; large text 3 to 1). Graphics that you need to understand the chart (bars, lines, axis ticks, focus rings) need 3 to 1 against what is next to them (1.4.11 Non-text Contrast, AA). The ratio comes from each colour's relative luminance: (lighter + 0.05) / (darker + 0.05). The first example computes it for the dashboard's tokens in both themes.

  3. 3

    Colour must not be the only signal. WCAG 1.4.1 Use of Color (level A): if a bar is orange because it is the quiet day, something else must say so as well: a label, a note, a pattern, or the words in the description. The first example finds that the dashboard's orange and teal have almost the same lightness (1.00 to 1), so someone who sees only lightness cannot tell them apart. Phase 10 chose six colours that stay apart under four kinds of colour vision (Topic 10.2); lightness is a separate check.

  4. 4

    Zoom and reflow. People zoom to 200% to read text (WCAG 1.4.4 Resize Text, AA) and to 400%, which is a 320 CSS pixel wide window (1.4.10 Reflow, AA). The page must reflow into one column with no sideways scrolling for the main content. The dashboard's charts are drawn at their box's width and redraw on resize (Topic 10.5), so they reflow. Measure it: the block below shows the page at 320 pixels.

  5. 5

    Respect reduced motion. Some people get dizzy or distracted from motion, and set their system to reduce it. The CSS query prefers-reduced-motion: reduce reports it. The dashboard's one function ms() returns 0 for every duration then, so the line appears at once and the bars do not slide (Topic 9.5). WCAG 2.3.3 Animation from Interactions (level AAA) covers motion that a click triggers, and 2.2.2 Pause, Stop, Hide (level A) covers anything that moves or updates by itself for more than five seconds: Phase 16's live feed needs a pause button.

  6. 6

    Sound is another way to see a trend. Sonification turns values into pitch: high numbers are high notes. A person hears the shape of a week in two seconds. Browsers only start sound after a person's click, so the code path is a button that plays notes through Web Audio (AudioContext, an oscillator and a gain for each note). The third example plans the notes and logs the frequencies. Sound helps some people and annoys others: it is an extra, never the only way. The Highcharts library ships an accessibility module and a sonification module, which is a useful reference for how a mature chart library packages these.

Explain it without notes

01

What are the two contrast rules for a chart and what ratios do they need?

02

Why can colour not be the only signal?

03

How does the dashboard respect reduced motion in one place?

04

Why does sonification wait for a click?

Practice

01

Compute the contrast of #767676 text on white. Is it enough for body text?

02

Write a function playOne(hz) that plays one half-second note and fades it out.

Trade-offs

  • ↔

    A strong focus ring and strong mark colours cost some elegance. They are the first thing a low-vision reader notices.

  • ↔

    Sonification costs code and attention for a feature some readers love and others ignore. Offer it as an option and keep it off by default.

Done when you can

  • I can compute a contrast ratio and know which rule needs 3 and which needs 4.5.

  • I never use colour alone to say something.

  • I have checked the page at 320 CSS pixels for sideways scrolling.

  • I send every animation duration through one reduced-motion check.

  • I know sound starts only after a click and is never the only route.