Accessibility · Testing Accessibility
Testing Reflow, Zoom, and Text Spacing
How the reflow, resize-text, and text-spacing criteria turn layout questions into arithmetic on viewport width, scroll extents, and computed font metrics.
Three success criteria look like layout preferences and are actually arithmetic. Reflow sets a viewport width and asks whether content requires scrolling in two directions. Resize text sets a scaling factor and asks whether content is lost. Text spacing sets four spacing values and asks whether content is clipped. Each converts to a measurement, and each measurement has a well-defined failure signature that a browser can be driven to produce.
The interesting engineering is in the boundary conditions. All three criteria have exceptions, and the exceptions are where the tests either become honest or become theatre.
Reflow: 320 CSS pixels and one axis
The criterion requires that content can be presented without loss of information or functionality, and without scrolling in two directions, at a width equivalent to 320 CSS pixels. The equivalence is defined against a 1280-pixel-wide viewport at 400 percent zoom, which is the standard desktop width divided by four. Content that scrolls vertically is fine; content that requires horizontal scrolling to read a line of text is the failure.
The test is a measurement of scroll extents at that width. Set the viewport to 320 pixels, load the page, and compare document.documentElement.scrollWidth against the client width. A page whose scroll width exceeds its client width by more than the width of a scrollbar has horizontal overflow somewhere, and the next step is to find the element responsible: walk the tree for elements whose getBoundingClientRect().right exceeds the viewport width, or whose own scrollWidth exceeds their clientWidth.
Not all overflow is a failure. The criterion’s exceptions cover content that requires two-dimensional layout for its meaning: data tables, maps, video, diagrams, and interfaces where the spatial relationship is the information. A wide table that scrolls horizontally inside its own container is permitted. A paragraph of text that scrolls horizontally is not. The distinction is about whether the content’s meaning survives reflow, which is a judgement, but the mechanical part is that the scroll happens in a container the author created rather than in the page.
Two implementation details break naive tests. The first is that a page can have overflow-x: hidden on the root, which hides the overflow from scrollWidth measurement while leaving the content inaccessible: the text is clipped and unreachable. The test must therefore check for clipping as well as for scrollbars, by comparing each text block’s scroll width to its client width. The second is that fixed-position elements are laid out against the viewport and do not participate in the document’s scroll extent, so a sticky header that overflows horizontally will not show up in the root measurement but will still cover content.
// Find elements whose content is clipped or that push past the viewport.
const offenders = await page.evaluate(() => {
const vw = document.documentElement.clientWidth;
const out = [];
for (const el of document.querySelectorAll('*')) {
const r = el.getBoundingClientRect();
const clippedX = el.scrollWidth > el.clientWidth + 1;
const outside = r.right > vw + 1 || r.left < -1;
const hidesOverflow = getComputedStyle(el).overflowX !== 'visible';
if (clippedX && hidesOverflow) out.push({ el: el.tagName, kind: 'clipped', w: el.scrollWidth });
else if (outside && r.width > 0) out.push({ el: el.tagName, kind: 'outside', right: Math.round(r.right) });
}
return out.slice(0, 40);
});
The output needs filtering before it is a defect list. A horizontally scrolling code block reports as clipped and is permitted. A carousel reports as clipped and is permitted if it has controls. The filter is by role and by content type, which is why the reflow test is usually paired with a list of known-permitted containers.
Resize text: 200 percent without loss
The criterion requires that text can be resized up to 200 percent without loss of content or functionality. It applies to text scaling rather than to page zoom: the browser’s text-size setting increases font size while leaving layout widths alone, which is a different stress than zoom, where everything scales together.
Testing it means overriding the root font size and observing whether anything is lost. On a page whose type is specified in rem and whose containers grow, the result is a taller page and nothing lost. On a page whose type is in pixels and whose containers have fixed heights, the result is clipped text: a card with height: 120px and overflow: hidden will cut off its third line of text at 200 percent.
The measurement is a comparison of each text container’s scrollHeight against its clientHeight with the scaled font size applied, plus a check that the container does not hide its overflow. Elements that legitimately scroll, such as a code block with a fixed max height, are exceptions; elements that clip text are failures.
Zoom is a related but distinct test and it catches different defects. At 400 percent page zoom the CSS viewport shrinks to a quarter of its width, which is how the reflow criterion’s 320-pixel figure is derived. A layout that uses viewport units for type, font-size: 4vw for example, does not grow under zoom in the way the user expects, because the viewport unit is relative to the shrunken viewport; the text stays physically small while everything around it grows. This is a real and common defect and it only appears under zoom, not under text scaling.
Text spacing: four overrides and a clipping check
The criterion specifies that no loss of content occurs when the following are set: line height to at least 1.5 times the font size, spacing after paragraphs to at least 2 times the font size, letter spacing to at least 0.12 times the font size, and word spacing to at least 0.16 times the font size. The values are minimums and the test applies them as overrides, not as design values.
The mechanism of failure is that a container sized for the original metrics cannot hold the expanded text. A single-line button with a fixed height clips its label when letter spacing pushes it wider. A navigation bar with a fixed height wraps to two lines and hides the second. A table cell with white-space: nowrap and a fixed width overflows its column.
The test is the same clipping measurement as the resize test, applied after injecting the overrides. The overrides are straightforward CSS and can be applied to the document as a style rule, with the four properties set to the specified multiples. Because the overrides apply to the whole document, the measurement finds every container whose content now exceeds it.
/* The four overrides the criterion specifies, applied as a test stylesheet. */
* {
line-height: 1.5 !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
p, li, blockquote { margin-bottom: 2em !important; }
Two nuances make the difference between a useful test and a noisy one. The first is that !important on letter-spacing and word-spacing applies to every element including icons rendered as text and including elements whose spacing was deliberately negative for kerning; the report will contain noise from typographic elements that are not prose. The second is that the criterion applies to blocks of text, not to every element, so the measurement should be restricted to elements that contain text nodes and that are not marked as decorative.
The failure signature worth recording is not the count of overflowing elements but the identity of the containers that clip. A design that uses intrinsic sizing throughout passes; a design with fixed heights in a few components fails in exactly those components, and the list is short and actionable.
What these three tests share
All three are layout stress tests, and all three reduce to the same primitive: apply a transformation that increases the space text needs, then check whether the container gave the text the space or hid it. The transformation differs (narrower viewport, larger font, more spacing) and the measurement is the same pair of numbers, content extent against box extent.
The primitive has a blind spot that is worth stating. A container that grows to fit its text passes every one of these tests and can still be unusable, because the growth may push the page into a shape that no longer works: a 200 percent text size that turns a two-column form into a single column of extremely tall fields is conforming and awkward. The criteria are about loss, not about quality, and the tests measure loss.
The other blind spot is state. These measurements are taken on a rendered page in a particular state, and a component that passes in its default state can clip in an error state where the label is longer, or in a loading state where the content is a placeholder of a different size. The interaction coverage argument applies: a layout stress test is only as good as the states it was run against, and the states that matter are the ones with the longest strings.
Where the tests genuinely help is regression. Each produces a small, stable list of offending containers, and the list is diffable. A change that introduces a fixed height into a component that previously used intrinsic sizing shows up as a new entry with the component’s identity attached, and the reporting discipline that keeps the list short is what makes it readable.