Line Length and Readability: WCAG and Beyond
Line length is the horizontal measure of a container that holds multi-line text. When one line ends too far from where the next begins, readers can lose their place, skip lines, or have to reread. The Web Content Accessibility Guidelines (WCAG) sets a hard ceiling: no more than 80 characters per line (40 for Chinese, Japanese, or Korean). That's easily implemented with character (ch) units:
width: 80ch;
Because 1ch equals the width of the digit 0 in the current font, the resulting length changes with font choice. But 80 characters is a maximum, not a target. Research from the Baymard Institute puts the sweet spot at 50–75 characters per line, balancing readability against the cost of too many short lines.
A hard minimum like min-width: 50ch breaks responsive layouts—a 320px viewport won't reliably fit 50 characters. The practical approach combines clamp() with min():
clamp()picks a fluid value within minimum and maximum constraints.min()resolves to the smallest value in a comma-separated list.
Inside min(), one argument can be a viewport-relative value like 93.75vw: at 320px wide that's 300px, leaving 20px of breathing room; at 1440px it's 1350px. The other argument, 50ch, wins whenever it's the smaller one.
min(93.75vw, 50ch);
Passing this min() expression into clamp() as the minimum value sets the line length conditionally. The middle argument is the preferred width when neither bound applies, and the maximum caps the layout at 75ch.
width: clamp(min(93.75vw, 50ch), 70vw, 75ch);
The three arguments can themselves contain min(), max(), or calc() for finer nuance. If the reading column feels narrow or wide relative to what the content should support, adjust font-size—a container too narrow usually means the font is too large, and vice versa.
Fitting Headline Text Width to a Container
Toward the goal of stretching headings across the full width of a container, JavaScript remains the shortest practical route—about three to five lines. An SVG approach conveniently handles this, since the SVG inherits CSS properties including color through fill: currentColor. Using known viewBox values keeps the approach contained:
<h1 class="container">
<svg>
<text>Fit text to container</text>
</svg>
</h1>
h1.container {
/* Container size */
width: 100%;
/* Type styles (<text> will inherit most of them) */
font: 900 1em system-ui;
color: hsl(43 74% 3%);
text {
/*
We have to use fill: instead of color: here
But we can use currentColor to inherit the color
*/
fill: currentColor;
}
}
/* Select all SVGs */
const svg = document.querySelectorAll("svg");
/* Loop all SVGs */
svg.forEach(element => {
/* Get bounding box of <text> element */
const bbox = element.querySelector("text").getBBox();
/* Apply bounding box values to SVG element as viewBox */
element.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" "));
});
A CSS-Only Fit-to-Container Workaround
For a solution without JavaScript, Roman Komarov's fit-to-width technique works, though it is intricate. The core steps:
- Duplicate the text (invisibly with
visibility: hidden, and accessibly hidden viaaria-hidden) so the duplicates perform math on the visible text's behalf. - Container query units compute the ratio of the text's inline size to the container's inline size, generating a scaling factor.
tan(atan2())type-casting converts that ratio into a unitless number CSS can apply.- Some custom properties must be registered with
@propertyor they won't work as custom properties normally would. - Elements are sized by applying the factor to
font-size, optionally bounded withclamp()for minimum and maximum sizes.
<span class="text-fit">
<span>
<span class="text-fit">
<span><span>fit-to-width text</span></span>
<span aria-hidden="true">fit-to-width text</span>
</span>
</span>
<span aria-hidden="true">fit-to-width text</span>
</span>
.text-fit {
display: flex;
container-type: inline-size;
--captured-length: initial;
--support-sentinel: var(--captured-length, 9999px);
& > [aria-hidden] {
visibility: hidden;
}
& > :not([aria-hidden]) {
flex-grow: 1;
container-type: inline-size;
--captured-length: 100cqi;
--available-space: var(--captured-length);
& > * {
--support-sentinel: inherit;
--captured-length: 100cqi;
--ratio: tan(
atan2(
var(--available-space),
var(--available-space) - var(--captured-length)
)
);
--font-size: clamp(
1em,
1em * var(--ratio),
var(--max-font-size, infinity * 1px) - var(--support-sentinel)
);
inline-size: var(--available-space);
&:not(.text-fit) {
display: block;
font-size: var(--font-size);
@container (inline-size > 0) {
white-space: nowrap;
}
}
/* Necessary for variable fonts that use optical sizing */
&.text-fit {
--captured-length2: var(--font-size);
font-variation-settings: "opsz" tan(atan2(var(--captured-length2), 1px));
}
}
}
}
@property --captured-length {
syntax: "<length>";
initial-value: 0px;
inherits: true;
}
@property --captured-length2 {
syntax: "<length>";
initial-value: 0px;
inherits: true;
}
Proposed Native Properties: text-grow and text-shrink
For future one-line CSS fitting, two dedicated properties—text-grow and text-shrink—are in discussion. Chrome intends to prototype them. A notable advantage over current techniques: they can apply to multiple lines of wrapped text, whereas the JavaScript and pure-CSS approaches require one container per line.
Syntax would be shared by both properties, with three arguments. If behavior should allow both growing and shrinking, both properties need to be used:
text-grow: <fit-target> <fit-method>? <length>?;
text-shrink: <fit-target> <fit-method>? <length>?;
<fit-target>per-line: Fortext-grow, short lines stretch to meet the container; fortext-shrink, overlong lines compress to fit.consistent: Fortext-grow, every line scales by the same factor derived from the shortest line; fortext-shrink, the factor derives from the longest line.<fit-method>(optional)scale: Scales glyphs uniformly without altering layout-drivenfont-size.scale-inline: Same asscale, horizontally only.font-size: Adjusts computedfont-sizeinstead of transforming rendered glyphs.font-size: Changesfont-sizeitself (likely the logical default).letter-spacing: Alters letter spacing to fill space rather than glyph metrics.<length>(optional): Caps the result—a maximum size fortext-grow, a minimum fortext-shrink.
While this suits many scenarios, font-size: fit-width already presents a simpler, single-property alternative: one line of CSS scales all lines to fit at once. Until any of these ship, work continues through the GitHub issue discussing the feature for feedback on tests and use cases.



