Text Spacing Override Testing for 1.4.12
Permalink to "Text Spacing Override Testing for 1.4.12"Users with dyslexia and low vision often increase line height, letter spacing and word spacing with browser extensions or user stylesheets. WCAG 1.4.12 requires that content does not lose information or functionality when they do. Data grids are among the most fragile components here: fixed row heights clip the second line, overflow: hidden cuts off longer words, badges overflow their pills and column headers overlap sort icons. The criterion is easy to test automatically.
This guide belongs to visual regression & theme testing.
Spec reference
Permalink to "Spec reference"SC 1.4.12 Text Spacing (AA) — no loss of content or functionality occurs when users 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;
- word spacing to at least 0.16 times the font size.
Content does not need to look good, but nothing may be cut off, overlap so it cannot be read, or become unusable.
When to use — and when not to
Permalink to "When to use — and when not to"Run text spacing tests on every component with constrained text: grid cells and headers, badges, chips, buttons, tabs, tooltips, KPI cards. Combine with 200% zoom for the strictest case.
Do not “pass” 1.4.12 by ignoring user styles (for example, !important on line-height in components). That blocks the very adjustments the criterion protects. Truncation with an ellipsis is acceptable only if the full text remains available (tooltip on focus, expandable row); clipping without access is a failure.
The misapplication: a compact grid with height: 28px; overflow: hidden on every cell, which clips descenders even before overrides are applied.
Annotated code example
Permalink to "Annotated code example"// text-spacing.spec.js
const SPACING = `* { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; }
p { margin-bottom: 2em !important; }`;
test('grid survives text spacing overrides (SC 1.4.12)', async ({ page }) => {
await page.goto('/invoices');
await page.addStyleTag({ content: SPACING });
await page.waitForTimeout(100); // allow layout
const clipped = await page.evaluate(() => [...document.querySelectorAll('td, th, .badge, button, [role="tab"]')]
.filter(el => {
const cs = getComputedStyle(el);
const hides = ['hidden', 'clip'].includes(cs.overflowY) || ['hidden', 'clip'].includes(cs.overflowX);
const truncatedOk = cs.textOverflow === 'ellipsis' && el.hasAttribute('data-full-text'); // full text available
return hides && !truncatedOk && (el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1);
})
.map(el => el.textContent.trim().slice(0, 40)));
expect(clipped).toEqual([]);
await expect(page.getByRole('table', { name: 'Invoices' })).toHaveScreenshot('invoices-text-spacing.png');
});
/* Fixes that make the test pass */
.grid td, .grid th { min-block-size: 2rem; block-size: auto; overflow: visible; } /* rows grow */
.badge { display: inline-flex; padding-block: .125em; line-height: inherit; } /* pill grows */
.grid th .sort-icon { flex: none; } /* icon keeps its space */
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"Text spacing does not change semantics, but clipped content often hides interactive elements:
| Failure under overrides | User impact | Fix |
|---|---|---|
| Row action button pushed outside a fixed-height row | Button unreachable visually; focus ring clipped | Rows grow; overflow visible |
| Sort icon overlapping header text | Header unreadable | Flex layout with wrapping |
| Badge text clipped | Status unreadable | Pill grows with text |
| Tab labels overlapping | Tabs unreadable | Allow wrapping or scrolling tablist |
Integration context
Permalink to "Integration context"Text spacing interacts with density settings — see segmented controls for view density. Truncation must keep full text available, as in accessible tooltips for truncated cell text. Zoom testing is covered in zoom and reflow testing at 400 percent.
Gotchas
Permalink to "Gotchas"Virtualized grids with fixed row heights. Many virtualizers require a fixed row height for performance. Use dynamic measurement (most libraries support it) or reserve enough height for 1.5 line height with two lines.
Canvas grids. Text drawn on canvas ignores user styles entirely, so 1.4.12 overrides have no effect — which also means users cannot adjust spacing at all. Canvas grids need their own spacing settings.
Icons sized in em. Icons that grow with letter spacing can push layouts; size icons in rem or px within flexible containers.
Common mistakes
Permalink to "Common mistakes"- Fixed row and pill heights.
!importantline-height in components, blocking user styles.- Clipping without a way to reach full text.
- Testing only the default density.
Framework notes
Permalink to "Framework notes"AG Grid supports getRowHeight and auto-height rows (autoHeight: true on columns) — enable them, or test that fixed heights accommodate the overrides. MUI X has getRowHeight={() => 'auto'}. TanStack Virtual supports dynamic measurement with measureElement. In Storybook, a decorator can inject the spacing stylesheet for a “Text spacing” story variant.
// AG Grid: rows grow with their content
const columnDefs = [{ field: 'customer', wrapText: true, autoHeight: true }];
Worked example
Permalink to "Worked example"An admin grid used 28-pixel fixed rows and pill badges with fixed height. The spacing test flagged 52 clipped elements on one page. Switching rows to minimum heights with auto growth, making badges inline-flex with padding, and letting headers wrap reduced the count to zero. The compact density still works by default; it simply grows when users need more space.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Ban fixed heights on text containers in component styles (lint for height: on cells, badges and buttons), and add a text-spacing story variant to every component. Make 1.4.12 part of component review.
FAQ
Permalink to "FAQ"Does truncation with an ellipsis fail 1.4.12?
Not if the full text is available another way, such as a focus tooltip or an expandable row. Silent clipping fails.
Do I need to look good under overrides?
No — only avoid loss of content or functionality. Layouts can look looser.
Should components resist user styles?
No. Avoid !important on typography in component CSS; let user styles apply.
How do I test canvas-rendered grids?
Overrides do not reach canvas text. Offer in-app spacing settings and test those.
Is a bookmarklet enough for manual checks?
For spot checks, yes. Automate the same stylesheet in tests for regression coverage.
Does 1.4.12 apply to placeholder text and labels inside inputs?
Yes. Inputs need enough height for increased line height; fixed-height inputs often clip labels.
Which elements should the clipping scan include?
Any element that contains text and might constrain it: cells, headers, badges, chips, buttons, tabs, tooltips and inputs. Start broad and exclude known-safe containers as you learn your codebase.
Related
Permalink to "Related"- Testing target size in dense grids — the other dense-grid risk
- Truncated cell tooltips — keeping full text available
- Zoom and reflow testing at 400% — combined stress tests