Section 4 · Visual Design and Content
Zoom, Reflow, Target Size and Motion
17 min4 min readCode playground
Lesson at a glance
Keep Ticketly usable at 400% zoom and with custom text spacing, size touch targets properly, and respect users who need less motion with prefers-reduced-motion and pause controls.
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Maha reads Ticketly at 400% zoom on a 13-inch laptop. At that zoom, her browser window is effectively 320 CSS pixels wide. The festival cards, laid out in three fixed 320px columns, force her to scroll sideways on every line of text, which at that magnification means losing her place constantly. Meanwhile the homepage marquee scrolls artist names forever, which gives her colleague Tom, who has a vestibular disorder, a headache within a minute.
This lesson covers what happens when people change the size of your page, and when your page moves on its own.
Zoom and reflow
Three criteria work together:
- 1.4.4 Resize Text (AA): text can be zoomed to 200% without losing content or function.
- 1.4.10 Reflow (AA): at a width of 320 CSS pixels, content fits without horizontal scrolling. (A 1280px window at 400% zoom is 320 CSS pixels wide.) Data tables, maps and diagrams are exempt, because they need two dimensions.
- 1.4.12 Text Spacing (AA): nothing breaks when users override line height to 1.5, paragraph spacing to 2em, letter spacing to 0.12em and word spacing to 0.16em, as dyslexic readers often do with a browser extension.
If you build responsively, you are most of the way there. The common culprits are fixed widths and fixed heights. Ticketly’s card grid:
.cards {
display: grid;
grid-template-columns: repeat(3, 320px);
gap: 16px;
}
.card h3 {
height: 24px; /* clips the title when text spacing grows */
font-size: 18px; /* doesn't follow the user's default font size */
}.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
.card h3 {
font-size: 1.125rem;
}The min(100%, 16rem) part matters: on a 320px viewport a 16rem column (256px plus padding) could still overflow, and min() lets the column shrink to the container. Sizing text in rem means it scales when a user sets a larger default font size in their browser, which zoom alone doesn’t cover.
To test reflow, set your browser window to 1280px wide and zoom to 400%, or use responsive mode at 320px. Scroll vertically through the whole page. Any horizontal scrollbar, overlapping text or cut-off button is a bug.
Target size, revisited
You met 2.5.8 Target Size (Minimum) in WCAG 2.2, POUR and the Law: pointer targets of at least 24 by 24 CSS pixels, or enough spacing around smaller ones. Ticketly’s quantity stepper used 16px icon buttons touching each other. The fix is padding, not bigger icons:
.stepper button {
min-inline-size: 2.75rem; /* 44px, comfortably above the 24px minimum */
min-block-size: 2.75rem;
}Aim higher than the minimum for primary actions on touch screens. Apple’s and Google’s platform guidelines suggest around 44 to 48 pixels, and people with tremor will thank you.
Motion: pause it, and respect the preference
Movement causes two different problems. Content that moves on its own competes for attention, which is a real barrier for people with attention disorders and anyone reading slowly. And large motion, such as parallax, zooming transitions or sliding panels, can trigger dizziness and nausea in people with vestibular disorders.
WCAG addresses the first with 2.2.2 Pause, Stop, Hide (A): anything that starts automatically, moves (or blinks or scrolls) for more than five seconds and appears alongside other content needs a way to pause, stop or hide it. 2.3.1 Three Flashes or Below Threshold (A) forbids content that flashes more than three times in any one second, unless the flashes are small and dim enough to stay under the flash thresholds, because flashing can trigger seizures.
For the second, operating systems let users ask for reduced motion (on macOS: Accessibility, Display, Reduce motion; on Windows: Accessibility, Visual effects, Animation effects off), and CSS can read that setting:
.marquee__track {
animation: scroll-names 20s linear infinite;
}
.marquee--paused .marquee__track {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.marquee__track {
animation: none;
}
.card {
transition: opacity 0.2s; /* a fade instead of a slide */
}
}<button type="button" class="marquee-toggle" aria-pressed="false">Pause animation</button>The pause button is still needed: many people who are bothered by motion never find the system setting, and 2.2.2 requires a control on the page. “Reduced” doesn’t have to mean “none”. A short fade or colour change rarely causes trouble; it is large movement across the screen that does.
JavaScript-driven animations need the same check. window.matchMedia('(prefers-reduced-motion: reduce)').matches tells you the current preference, and you can listen for its change event to react if the user changes it while the page is open.
Ticketly now holds together when people zoom it, space its text, tap it with a shaky hand or ask it to keep still. The next lesson returns to the accessibility tree and looks closely at how every control gets the name a screen reader speaks.
Key takeaways
- Content must reflow into a 320 CSS pixel wide viewport, the width of a 1280px window at 400% zoom, without horizontal scrolling.
- Size text in rem, avoid fixed heights on text containers, and never disable pinch zoom in the viewport meta tag.
- Anything that moves on its own for more than five seconds needs a way to pause, stop or hide it (2.2.2).
- Wrap non-essential animation in a prefers-reduced-motion check, replacing big movement with fades or nothing.