---
title: 'Line Length and Spacing'
source: 'https://academia.sh/en/courses/interface-fundamentals/line-length-and-spacing'
course: 'Fundamentals of Interface Design'
language: en
updated: '2026-08-17T18:11:57+00:00'
license: 'CC BY-SA 4.0'
---

# Line Length and Spacing

Computing line length in characters from column width, tying leading to line length, the return-sweep-angle criterion, and the lower bound on paragraph spacing.

The scale gave the size of the text. The summary paragraph on the record detail screen
takes a number from the scale — 16 pixels, step zero — and spreads across the full
column the layout gives it. On a wide screen, that column opens up to 880 pixels. Is
this paragraph readable?

The question falls on the second side of the distinction defined in the previous
lesson. The typeface decision is made, the size decision is made; both were on the
legibility side. Readability, however, is measured at the block level and depends on
two quantities: how long a line is and how far apart the lines sit from each other.
These two quantities are not independent; when one changes, the other has to change
too.

## Line Length Is a Character Measure, Not a Pixel Measure

**Line length** is the number of characters that fit on a line. The reason it is
measured in characters rather than pixels is that the distance the eye covers returning
from the end of a line to the start of the next relates to word count; the same pixel
width carries a different number of words at different type sizes.

Character count cannot be measured directly, because characters have different widths.
A typeface does, however, declare each character's **advance width**; it can be
computed by averaging over real text.

```js
// linelength.mjs — computing line length in characters from column width

// Advance widths the candidate family declares (em = 1000 units).
const G = {
  " ": 260, a: 556, b: 574, c: 500, d: 574, e: 545, f: 340, g: 574,
  h: 560, i: 244, j: 244, k: 520, l: 244, m: 856, n: 560,
  o: 570, p: 574, q: 574, r: 366, s: 480, t: 358, u: 560,
  v: 508, w: 800, x: 500, y: 508, z: 470, A: 660, B: 660, C: 686,
  D: 720, E: 610, F: 610, G: 740, H: 730, I: 280, J: 280, K: 660,
  L: 610, M: 890, N: 730, O: 760, P: 610, R: 660, S: 620,
  T: 620, U: 720, V: 660, W: 890, X: 660, Y: 620, Z: 600,
  ",": 260, ".": 260, ":": 260, "-": 340, "1": 560, "9": 560,
  "0": 560, "8": 560,
};
const DEFAULT = 560;

const summary =
  "This volume traces the development of optics together with the changing " +
  "instruments of observation. The author first frames light and shadow as a " +
  "problem of measurement, then shows how theories of vision follow from these " +
  "measurements. The bibliography is extensive, the index detailed, and the " +
  "plates are taken from original prints.";

const total = [...summary].reduce((s, c) => s + (G[c] ?? DEFAULT), 0);
const average = total / [...summary].length;

const SIZE = 16; // px
const averagePx = (average / 1000) * SIZE;

console.log(`summary text character count: ${[...summary].length}`);
console.log(`average advance width: ${average.toFixed(1)} / 1000 em`);
console.log(`average character width at 16 px: ${averagePx.toFixed(3)} px\n`);

const TARGET = [45, 75];
console.log("column (px)  line length (characters)  target 45-75");
for (const column of [320, 480, 560, 640, 720, 880, 1040]) {
  const characters = column / averagePx;
  const status = characters < TARGET[0] ? "SHORT" : characters > TARGET[1] ? "LONG" : "fits";
  console.log(`${String(column).padStart(11)} ${characters.toFixed(1).padStart(26)}  ${status}`);
}

console.log("\ntarget character count -> required column width");
for (const k of [45, 66, 75]) {
  console.log(`${String(k).padStart(3)} characters -> ${(k * averagePx).toFixed(1)} px`);
}

// If size changes at a fixed column width, line length changes too.
console.log("\ncolumn fixed at 640 px  ->  size  line length (characters)");
for (const b of [13, 16, 20, 25]) {
  const width = (average / 1000) * b;
  console.log(`${String(b).padStart(24)} px ${(640 / width).toFixed(1).padStart(24)}`);
}
```

```
summary text character count: 333
average advance width: 455.9 / 1000 em
average character width at 16 px: 7.295 px

column (px)  line length (characters)  target 45-75
        320                       43.9  SHORT
        480                       65.8  fits
        560                       76.8  LONG
        640                       87.7  LONG
        720                       98.7  LONG
        880                      120.6  LONG
       1040                      142.6  LONG

target character count -> required column width
 45 characters -> 328.3 px
 66 characters -> 481.4 px
 75 characters -> 547.1 px

column fixed at 640 px  ->  size  line length (characters)
                      13 px                    108.0
                      16 px                     87.7
                      20 px                     70.2
                      25 px                     56.2
```

The 45–75 character range is the criterion this course adopts and quantifies so it can
be audited; the lower bound keeps lines from breaking too often, the upper bound keeps
the return sweep from becoming difficult.

The output's second block is the real finding. An 880-pixel column fits 120.6
characters, roughly 1.6 times the upper bound. When the summary paragraph spreads across
the full column, it misses the readability criterion not at a single point but across
the entire span of a wide screen. Every column width from 560 pixels up falls outside
the criterion.

The third block gives the measure of the solution. Whatever the column's pixel width,
the summary text's width should be at most 547 pixels. This is a constraint independent
of the layout's column width: the text block carries its own maximum width, and if the
column is wider than that, the text does not fill the column.

The fourth block exposes a trap. Holding the column fixed and changing the size still
changes line length: in a 640-pixel column, 108.0 characters at 13 pixels, 56.2 at 25
pixels. Although line length looks like a layout decision, it is a direct consequence
of the step chosen from the scale. A heading's line length and body text's line length
differ even in the same column, and the upper bound is audited separately for each
level.

The constraint's written form is a maximum width given to the text block, not the
column:

```css
.summary {
  font-size: 16px;
  max-width: 547px;
}
```

This value also has a unit that can be written in characters: the `ch` unit uses the
advance width of the digit zero in the current typeface. Because zero's width is not
equal to the average character width, a number given in `ch` does not equal the
computed character count; this lesson computes the measure through the average and
writes the result in pixels.

## Leading Depends on Line Length

**Leading** is the distance between the baselines of two consecutive lines. In the
Visual Presentation with CSS course, this measure was given through the `line-height`
property and was called **line height** there; written unitless, it works as a
multiplier of the font size. On the design side, the question asked is what that
multiplier should be.

The multiplier has two separate lower bounds, and both are computable.

The first lower bound comes from the typeface. The sum of ascent and descent computed
in the previous lesson is the vertical measure the letters actually occupy. When the
multiplier drops below this value, the descenders of one line overlap the ascenders of
the next.

The second lower bound comes from eye movement. At the end of a line, the eye makes a
**return sweep** to the start of the next line. This movement's horizontal component is
line length, its vertical component is leading. Their ratio gives the angle at which
the sweep happens; as the angle shrinks, it becomes harder for the target line to stand
apart from its neighbors, and the eye reads the same line twice or skips a line.

```js
// leading.mjs — line box, inter-line space, return-sweep angle, and paragraph spacing

const SIZE = 16;             // px
const ASCENT_DESCENT = 1.051; // candidate-3's declared ascent+descent (em)
const ANGLE_THRESHOLD = 2.0;  // smallest return-sweep angle this course adopts (degrees)

const degrees = (rad) => (rad * 180) / Math.PI;

console.log("multiplier  line box   inter-line space   paragraph spacing >=");
for (const m of [1.2, 1.3, 1.4, 1.5, 1.6, 1.75]) {
  const box = m * SIZE;
  const space = box - ASCENT_DESCENT * SIZE;
  console.log(
    `${m.toFixed(2).padStart(10)} ${box.toFixed(2).padStart(14)} px ${space.toFixed(2).padStart(17)} px ${(space * 2).toFixed(2).padStart(17)} px`
  );
}

console.log("\nline length (px) -> return-sweep angle (degrees), by multiplier");
const multipliers = [1.2, 1.4, 1.5, 1.6, 1.75];
console.log("length   " + multipliers.map((m) => m.toFixed(2).padStart(7)).join(""));
for (const length of [328, 481, 547, 640, 880]) {
  const row = multipliers
    .map((m) => degrees(Math.atan((m * SIZE) / length)).toFixed(2).padStart(7))
    .join("");
  console.log(`${String(length).padStart(7)}  ${row}`);
}

console.log("\nline length -> smallest multiplier meeting the threshold (2.0 degrees)");
for (const length of [328, 481, 547, 640, 880]) {
  const fromAngle = (length * Math.tan((ANGLE_THRESHOLD * Math.PI) / 180)) / SIZE;
  // The multiplier cannot go below the ascent+descent value, or lines start to overlap.
  const lower = Math.max(fromAngle, ASCENT_DESCENT);
  const chosen = Math.ceil(lower * 20) / 20; // round to 0.05 steps
  console.log(
    `${String(length).padStart(4)} px -> from angle ${fromAngle.toFixed(3)}  lower bound ${lower.toFixed(3)}  chosen ${chosen.toFixed(2)}`
  );
}
```

```
multiplier  line box   inter-line space   paragraph spacing >=
      1.20          19.20 px              2.38 px              4.77 px
      1.30          20.80 px              3.98 px              7.97 px
      1.40          22.40 px              5.58 px             11.17 px
      1.50          24.00 px              7.18 px             14.37 px
      1.60          25.60 px              8.78 px             17.57 px
      1.75          28.00 px             11.18 px             22.37 px

line length (px) -> return-sweep angle (degrees), by multiplier
length      1.20   1.40   1.50   1.60   1.75
    328     3.35   3.91   4.18   4.46   4.88
    481     2.29   2.67   2.86   3.05   3.33
    547     2.01   2.34   2.51   2.68   2.93
    640     1.72   2.00   2.15   2.29   2.51
    880     1.25   1.46   1.56   1.67   1.82

line length -> smallest multiplier meeting the threshold (2.0 degrees)
 328 px -> from angle 0.716  lower bound 1.051  chosen 1.10
 481 px -> from angle 1.050  lower bound 1.051  chosen 1.10
 547 px -> from angle 1.194  lower bound 1.194  chosen 1.20
 640 px -> from angle 1.397  lower bound 1.397  chosen 1.40
 880 px -> from angle 1.921  lower bound 1.921  chosen 1.95
```

The two-degree threshold is likewise a criterion this course adopts; what matters is not
the number itself but that the decision is tied to a number.

The last block shows how the two lower bounds trade places. At 328 and 481 pixels, the
angle criterion is met by multipliers of 0.716 and 1.050; both sit below the 1.051 bound
the typeface imposes, so the typeface is decisive. From 547 pixels on, the angle
criterion takes over, demanding a multiplier of 1.921 at 880 pixels.

This confirms the previous section's finding by a second route. An 880-pixel line looks
"rescuable" by raising the leading multiplier to 1.95. But at 16-pixel text, a 1.95
multiplier means a 31.2-pixel line box; the gap between lines approaches the letters'
own height, and the block stops being a set of lines and turns into a series of separate
lines. The real fix for a long line is not to increase the leading, it is to shorten the
line.

The second table carries a warning in the opposite direction. At the short, 328-pixel
line, a 1.75 multiplier produces a return-sweep angle of 4.88 degrees. A large angle is
not itself a problem; but the inter-line space climbing to 11.18 pixels causes the lines
in a short-line block to come apart from each other. A short line wants little space, a
long line wants a lot of space; these are the two ends of a single rule.

## Paragraph Spacing Derives From Inter-Line Space

The last column of the first table gives a side effect of leading. Where one paragraph
ends and the next begins is legible only if the space between paragraphs is distinctly
larger than the inter-line space. Taken as double for a criterion, text set at a 1.5
multiplier needs a paragraph spacing of at least 14.37 pixels.

This is the typographic application of the relative-spacing principle established in
the Proximity and Grouping lesson: the ratio between within-group spacing and
between-group spacing determines the grouping. Here the group is the paragraph, and the
within-group spacing is the inter-line space.

The same rationale explains why paragraph indentation and paragraph spacing are not
used together. Both do the same job — announcing the paragraph boundary — and using
both at once marks the boundary twice. The two-channel rule does not apply here,
because when the paragraph boundary is lost, the cost the reader pays is not a loss of
hierarchy but merely a pause.

## Alignment and Breaking

Once line length is fixed, what remains is how line endings get broken.

In left-aligned text, the right edge is ragged and word spacing stays fixed. In
justified text, the right edge straightens, but word spacing stretches line by line. In
narrow columns this stretching grows and produces visibly empty channels within the
line. Because the catalog interface's summary paragraph can drop close to 45
characters, it is set left-aligned.

Centered text is unsuited to continuous reading, because every line starts at a
different point and the return sweep's target changes line by line. Centering can be
used for single-line or at most two-line text — an empty-state message, button text —
and these are treated in the Component States topic.

Word breaking reduces stretching in justified typesetting; in left-aligned
typesetting, it straightens the right edge but breaks word integrity. In the catalog
interface, author names and work titles are proper names and must not be broken;
breaking is therefore enabled only in the summary paragraph and stays off in the title
and metadata fields.

## Summary

- Line length is measured in characters; even if the pixel width stays the same, the
  character count changes when the type size changes.
- Character count is computed by averaging the typeface's declared advance widths over
  real text, and it is turned into a maximum-width constraint.
- The long-line problem is solved by a width limit given to the text block, not the
  column; the limit is computed separately for each text level.
- The leading multiplier has two lower bounds: the typeface's ascent-plus-descent sum
  and the return-sweep-angle criterion. The first is decisive on short lines, the
  second on long lines.
- Increasing the spacing does not rescue a long line; as the required multiplier grows,
  the block stops being a set of lines.
- Paragraph spacing must be distinctly larger than inter-line space; the paragraph
  boundary is announced by indentation or spacing, not both.

## Next Step

These three lessons fixed the form of the text: which family, which size, which width,
and which spacing. The color of text and ground, however, is still nothing more than
the dark gray and white from the first lesson. The interface's need for color is not a
decorative need: separating the primary action from the secondary action, announcing
that a record is selected, and showing shelf-availability status all require separate
surfaces. The next lesson defines colors not one by one but as roles, sets the roles
onto a scale along the lightness axis, and determines by computation which ground each
role will be matched with.
