Justified text fills each line by widening the spaces between words, and the narrower the column and the less hyphenation, the wider and more uneven those gaps get. On 472 real paragraphs, a 358px phone column gave a median gap 1.75 times a normal space and put 38% of lines over twice normal; at 600px it was 1.40 times and 12%. WCAG 1.4.8 (Level AAA) asks for text that is not justified. Justify only wide columns, with working hyphenation.

Key takeaways

  • Justification works by stretching spaces. Left-aligned text keeps every gap at one normal space; justified text widens the gaps on every line except the last.
  • Narrow columns make it much worse. Median gap: 1.40 times normal at 600px, 1.75 times at 358px, 2.10 times at 260px. Lines with gaps over twice normal: 12%, 38% and 54%.
  • The tail is where readers notice. At 358px the 95th-percentile gap was 3.60 times normal, which is the "river of white" WCAG describes.
  • WCAG 1.4.8 lists "not justified" at Level AAA. It is not a requirement of the usual AA target, and it is the criterion's stated concern for people with some cognitive disabilities.
  • Hyphenation could not be measured here. hyphens: auto had no effect in the headless Chromium used, so the numbers are justification without hyphenation, the worst case. Check it in the browsers your readers use.
  • A width-based rule is cheap: left-align by default and justify only when the column is wide enough.

What justification actually does

A browser can only make a line reach both margins by changing something. With text-align: justify it adds space between words. It does not shrink letters or move words to other lines to compensate, so a line that ended with a long word and a lot of leftover room gets very wide gaps, and the last line of a paragraph is left alone. Left-aligned text leaves the right edge ragged instead and keeps every gap the same.

The measurement

The test used every paragraph of at least 25 words from this site's article bodies: 472 paragraphs, 24,107 words. Each was laid out at four column widths in headless Chromium 141 with the article font size. For every line except the last, the script measured the average gap between words and divided it by the width of a normal space.

Word gap as a multiple of a normal space, justified text. Left-aligned text is 1.00 at every width.
Column width Median gap 95th percentile Lines over 2x normal
600px (a tablet or narrow desktop column)1.40x2.31x12.0%
358px (a 390px phone with gutters)1.75x3.60x37.8%
320px (a small phone)1.84x4.17x43.0%
260px (a very narrow column)2.10x5.42x53.7%

Two readings. First, the median is not the problem: a typical gap at phone width is under twice normal. The problem is the spread: at 358px more than a third of lines had gaps over double, and one line in twenty was over 3.6 times. Second, the extreme values (the maximum was about 30 times at 358px) come from lines holding one very long word, such as a URL, and should be treated as outliers, which is why the table uses percentiles.

What the standard says

WCAG 1.4.8 Visual Presentation, from W3C's Understanding document.
Item What it says
LevelAAA, so it is not part of an AA conformance target
Justification"Text is not justified (aligned to both the left and the right margins)"
Line widthNo more than 80 characters or glyphs
SpacingLine spacing at least 1.5 within paragraphs, paragraph spacing at least 1.5 times larger
Stated reasonPeople with certain cognitive disabilities have problems reading text that is both left and right justified, and uneven spacing can cause "rivers of white" to run down the page

That is a standard's position, not a measurement of any particular page. It is worth knowing for two reasons: it names a specific group of readers who are affected, and it is the same concern the 95th-percentile numbers above describe. The accessibility testing guide covers why this kind of issue is invisible to automated checkers.

The hyphenation caveat

Hyphenation is the standard remedy: breaking long words across lines gives the browser more places to end a line, so fewer lines need big gaps. hyphens: auto only works when the page declares its language and the browser has a hyphenation dictionary, according to MDN. In the headless Chromium used for this measurement, hyphens: auto produced numbers identical to plain justify, which means no dictionary was available. So the table above is the no-hyphenation case, the worst one. Desktop and mobile browsers with dictionaries will do better, and how much better is exactly what this test could not show.

What this site does. Article paragraphs use text-align: justify; text-justify: inter-word; hyphens: auto at every width, with the page language set. The measurement shows the cost is concentrated at phone widths, and whether hyphenation closes the gap in your own browsers is the thing worth checking on a real phone.

A rule you can apply

Where justified text is reasonable, and where it is not.
Situation Justify?
Wide column (roughly 600px and up) with lang set and hyphenation workingReasonable
Phone-width columnsLeft-align
Headings, captions, table cells, buttonsNo
Code and anything with long unbroken strings such as URLsNo
Readers you know are affected, or an AAA targetLeft-align everywhere
.article-body p { text-align: left; }

@media (min-width: 700px) {
  .article-body p {
    text-align: justify;
    text-justify: inter-word;
    hyphens: auto;
  }
}

The breakpoint is a judgment call: it exists so the narrow case gets the ragged edge and the wide case gets the tidy one. Pair it with a sensible line length and the spacing values from the standard, and check the result against the UI/UX checklist's typography items.

text-align: justify;
text-justify: inter-word;
hyphens: auto;

Measure your own pages

This console snippet prints the median word gap, as a multiple of a normal space, for one plain-text paragraph (it expects the paragraph's first child to be a single text node, so pick one without links or inline code):

function wordGap(p) {
  const node = p.firstChild;
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.font = getComputedStyle(p).font;
  const space = ctx.measureText(' ').width;
  const rects = [...p.textContent.matchAll(/\S+/g)].map(m => {
    const r = document.createRange();
    r.setStart(node, m.index); r.setEnd(node, m.index + m[0].length);
    return r.getClientRects()[0];
  });
  const lines = {};
  rects.forEach(r => (lines[Math.round(r.top)] ||= []).push(r));
  const gaps = Object.values(lines).slice(0, -1).filter(l => l.length > 1)
    .map(l => l.slice(1).reduce((s, r, i) => s + r.left - l[i].right, 0)
                / (l.length - 1) / space);
  return gaps.sort((a, b) => a - b)[Math.floor(gaps.length / 2)];
}
wordGap(document.querySelector('.your-paragraph'));

Run it at a phone width, then at a desktop width, with justification on and off. A value near 1 means the gaps are close to normal.

What this does not cover

Spacing is measured; readability is not. The test measures word gaps, not reading speed or comprehension, and it does so for one font size and one set of paragraphs in headless Chromium 141 without hyphenation. The WCAG text is the standard's position, and how much the rule matters for your readers is a judgment about them.

Badri Dutta

Software engineer · 8 years technical SEO

Written after this site's body text was set to justified and I wanted to know what that costs. The cost turned out to be concentrated at phone widths, and the one remedy that should help, hyphenation, was the one thing the test environment could not show.

Full background →