text-spacing-content-loss

Text stays readable when the user increases text spacing

automaticWCAG 1.4.12 (AA)confidence: mediumseverity: seriousperceivable

Applies the WCAG 1.4.12 text spacing in the browser and checks that no text is cut off or made to overlap, and asks about style sheet rules that force spacing with !important.

Applies to. Applies to a page with visible text. The loss of content needs a layout (a browser): without one, only the style sheets are read.

Expectation. WCAG 1.4.12: with line height at 1.5 times the font size, spacing after paragraphs at 2 times, letter spacing at 0.12 times and word spacing at 0.16 times, no content or functionality is lost.

  • In a browser, the spacing is applied as a style sheet that wins over the page's own (inline !important aside, which avoid-inline-spacing reports), and each line of text is measured before and after against the boxes that clip it: those on its containing block chain with overflow: hidden or clip, or contain: paint, and the viewport on an axis the page keeps from scrolling, or for text in a fixed box, which doesn't scroll with the page. A line that was inside and ends at least half outside (half its height, or half an em across) fails: the container cuts that text off (TEXT_CLIPPED). A line pushed out by less is asked about (TEXT_CLIPPED_PARTLY), and so is text that comes to overlap other text it did not overlap before (TEXT_OVERLAPS). Text moved by an animation that repeats for ever (a ticker) is asked about however far it goes (TEXT_CLIPPED_MOVING): it passes through the edge anyway, and one frame can't tell whether any of it is lost. Where the page is scroll-locked, both of the viewport's axes hidden while content already lay beyond it, as behind a cookie banner, text the spacing moves out of the viewport is asked about (VIEWPORT_SCROLL_LOCKED): the lock is usually a passing state, and the engine can't see past it. Text inside a <marquee> is left out: browsers move it outside the Web Animations API, so the engine can't hold it still, and its finding would depend on when the scan ran; deprecated-elements-not-used asks about the marquee (2.2.2). Text its box already cut off before the spacing (a fixed-height excerpt) is asked about when the spacing cuts off lines it showed in full (TEXT_CLIPPED_FURTHER): the excerpt was cut on purpose, but shows less. A box that grows with its lines, as line-clamp does, shows as many and is left alone.
  • In any environment, a style sheet rule that sets line-height, letter-spacing or word-spacing below those values with !important is asked about (STYLESHEET_IMPORTANT): a tool that adds its own style sheet to the page cannot override it, though a user style sheet can. A value relative to the font size is read as declared, and a computed one meets the value within the rounding browsers report lengths with, so a rule setting exactly the minimum is not asked about.
  • The spacing is applied with a <style> element, or, where the page's Content Security Policy blocks one, as a style sheet the document adopts. Where neither applies, the page is asked about (SPACING_NOT_APPLIED) instead of measured, never passed.

Without a layout and with no such rule, the rule is notApplicable, with data.reason: 'noLayout'.

  • Only text inside the scan's scope (contextSelector) and not excluded (excludeSelectors) is judged. Text elsewhere on the page is still measured, since text in scope can come to overlap it. While a modal dialog is open, the page behind it is left out altogether: the browser makes it inert, and the scan judges the dialog.
  • Margin (overflow-px): of the text a clipping box keeps in full, the line that came closest to being cut off. value is how far it reaches past the box's edge, negative while it is still inside, against 2px, past which a line is partly cut off (the first finding); context.axis and context.text say which edge and which text. It is measured where text grows with more spacing, the inline end (the right, or the left in right-to-left text) and the bottom; a start edge counts only once a line is past it. An axis on which the box grew with the spacing (a block of auto height) follows its content and is left out, and vertical text gets no margin. Text a repeating animation moves (a ticker) is left out, since where it stands depends on the point the animation was held at, and so is a box that cuts off other text: it is a finding. Only a line within 8px of the limit (from 6px inside the edge) counts as close, so text deeper inside its box gives no margin. measuredCount counts the pairs of text and clipping box compared; like the findings, the margin covers the first 3,000 text nodes.

What a finding reports.

  • text: the start of the text, up to 60 characters. Not on a style sheet rule's finding.
  • lines.shownBefore, lines.shownAfter (TEXT_CLIPPED_FURTHER): how many lines of the text its box showed in full without and with the spacing.
  • metrics.overflowPx (cut-off text): how far, in CSS pixels, the line went past the edge of the box that clips it, with the spacing applied.
  • metrics.thresholdPx (cut-off text): how far it may go before it fails: half an em across, or half the line's height down.
  • metrics.axis (cut-off text): x when the line went past a side, y past the top or bottom.
  • container.widthPx, container.heightPx (cut-off text): the size of the clipping box with the spacing applied.
  • viewport.width, viewport.height (cut-off or overlapping text): the viewport the page was laid out in, in CSS pixels. Text that fits at one width can be cut off at another.
  • other (overlapping text): the text it comes to overlap.
  • selector, property, value (a style sheet rule): the rule's selector and the declaration that forces the spacing.

These data.details fields help to read and reproduce a finding. Apart from reasonCode and the few fields Output schema names as stable, they are not a stable contract: a field may be renamed or dropped in a minor release, so don’t build on them.

Margin. This rule measures overflow-px in CSS pixels, and the value must stay under the threshold. When a pass came near the threshold, the result’s margin names the element that came closest while still meeting it, so a pass that a small change would flip shows before it flips. Unlike the fields a finding reports, it is a stable contract. See Output schema.

Examples

passIn a browser
<p>Opening hours today, and the rest of the week from nine to five.</p>

The paragraph has no fixed size, so the text grows with the spacing and nothing is lost.

failIn a browser
<style>
.hours { width: 19ch; height: 20px; overflow: hidden; }
</style>
<div class="hours">Opening hours today</div>

With line height 1.5, letter spacing 0.12em and word spacing 0.16em, the text wraps to a second line that the box cuts off. In jsdom, which has no layout, the rule reads only the style sheets and asks about spacing forced with !important.

How to check

When this rule can’t decide, it reports cantTell and leaves the question to a person. Each question it can ask is listed here under its reason code, with the steps to answer it. A finding carries the same steps as howToCheck, in the scan’s language. See Output schema.

SPACING_NOT_APPLIED

  1. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important. Rules added in the Styles pane are not blocked by the page's security policy.
  2. Scroll through the whole page and look for text that is cut off, overlaps other text or disappears.
  3. Where text is cut off, check whether the page offers a way to show all of it, such as a "Read more" link or a button that expands the box. It only counts if you can use it with the keyboard and, with the spacing still applied, it shows the whole text.
  4. Check that controls and menus still work and show all their text.

Pass. All text stays visible and readable, or any text that is cut off can be shown in full with the keyboard, and nothing stops working with the spacing applied.

Fail. Some text is cut off, overlaps or is lost with the spacing applied and cannot be shown in full another way, which fails WCAG 1.4.12.

STYLESHEET_IMPORTANT

  1. Note the rule, property and value the report gives, and find an element it styles in the Elements panel.
  2. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  3. Check in the Computed tab whether the element's line-height, letter-spacing and word-spacing now have those values.
  4. If they don't, give the element the same values in its own style (the element.style section at the top of the Styles pane), as a user style sheet would, and check whether its text stays readable.

Pass. A user's spacing still takes effect on the element, or the content reads fine without it.

Fail. The page's !important rule keeps the user's spacing from applying, which fails WCAG 1.4.12.

TEXT_CLIPPED_FURTHER

  1. Find the text excerpt the report names and note how many lines it shows in full.
  2. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  3. Count the lines it shows now, and check whether a 'read more' link or other way still reaches the full text.

Pass. The excerpt still does its job, and the full text can be reached another way.

Fail. The spacing cuts off content that cannot be reached any other way, which fails WCAG 1.4.12.

TEXT_CLIPPED_MOVING

  1. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  2. Find the moving text the report names and watch it through at least one full cycle of its animation.
  3. Pause it if there is a control, and check that all of its text can be read at some point.

Pass. Every part of the moving text can still be read during its cycle.

Fail. Some of the text never appears in full with the spacing applied, which fails WCAG 1.4.12.

TEXT_CLIPPED_SCRIPT_SIZED

  1. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  2. Find the text the report names and wait a moment: the page's own script may resize the box around it to fit the longer text.
  3. If it still looks cut off, make the browser window a little narrower or wider, or click somewhere empty on the page, which makes many pages size their boxes again.
  4. Read the text and check that all of it is shown.

Pass. Once the page has reacted, the box fits the text and all of it can be read with the spacing applied.

Fail. The text stays cut off with the spacing applied, even after the page has had time to react, which fails WCAG 1.4.12.

TEXT_CLIPPED_PARTLY

  1. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  2. Find the text the report names and look at the edge of the box that cuts it off.
  3. Read the text and check whether every letter can still be made out, including the tops and tails of letters.
  4. Where text is cut off, check whether the page offers a way to show all of it, such as a "Read more" link or a button that expands the box. It only counts if you can use it with the keyboard and, with the spacing still applied, it shows the whole text.

Pass. Only a sliver at the edge is cut and all the text can still be read, or the whole text can be shown with the keyboard another way, with the spacing still applied.

Fail. Part of the text is cut off so it can no longer be read, and it cannot be shown in full another way, which fails WCAG 1.4.12 (F104).

TEXT_OVERLAPS

  1. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  2. Find the text the report names and the text it now overlaps.
  3. Read both and check whether either one becomes hard to read where they overlap.

Pass. The overlap is only a sliver at the edges and both texts stay readable.

Fail. Text is drawn over other text so that either becomes unreadable, which fails WCAG 1.4.12 (F104).

VIEWPORT_SCROLL_LOCKED

  1. Close any banner, dialog or overlay that is open on the page, so the page can scroll.
  2. In the developer tools' Styles pane, add a new style rule for * setting line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important, and a rule for p setting margin-bottom: 2em !important.
  3. Scroll to the text the report names and check that it can be reached and read.

Pass. Once the overlay is closed, the page scrolls and all of the text can be reached and read.

Fail. The text stays out of reach with the spacing applied, which fails WCAG 1.4.12.

View source