FAQ

The usual questions about JavaScript typography.

Short answers, with the measurement behind each where there is one. The full contract, including what is not yet tested, is in SUPPORT.md.

Why not just use text-wrap: pretty?

Use it: it is the right baseline, and Typeset keeps it for readers without JavaScript. In Chrome and Safari it avoids one-word last lines. It does not know that “a”, “the” or “of” belong with the next word, and Firefox does not have it. Typeset adds grammar-aware breaks, the same result in all three engines, preserved links and styling, and a result your CI can check.

What do screen readers hear?

The same words as the source, with a line boundary at each generated break, as at any line end. The test suite reads the accessibility trees the browsers actually build, Chromium’s and WebKit’s on every run and Firefox’s nightly, and requires every composed paragraph’s words to match the source text in Chromium and Firefox, and every link and heading name in all three (WebKit exposes no paragraph text to the test). Text in or around a live region is never composed, so status messages are not announced again. Spoken VoiceOver and NVDA output has not yet been checked by a person; see SUPPORT.md.

Does it cause layout shift?

Rarely, and by one line at most. Typeset keeps the browser’s line count, except that body text may use one more line to fix a one-word last line or a stranded sentence opener. On narrow screens that line is common: about 1 in 6 body paragraphs take it at 320 pixels, 1 in 11 at 375, almost none on desktop. One taken in the first screen after the first paint is a small layout shift: about half of our test loads at phone widths recorded one, at most 0.05, under the 0.1 “good” threshold. mount() waits for web fonts before it composes.

Is it bad for SEO?

Not for indexing or ranking. The HTML your server sends is unchanged; Typeset adds line breaks in the browser only, and search engines index the same text. It does affect readers who arrive from a search: a Text Fragment link (#:~:text=), which Google uses to scroll to and highlight the passage a result quotes, misses a phrase that spans a generated break, and so does find-in-page. Measured in Chromium, WebKit and Firefox, 43 of 60 five-word phrases were found at 375 pixels and 52 of 60 at 768, against 60 of 60 uncomposed; single words are unaffected. For documentation and reference pages, where readers search and link to passages, mark the content data-no-typeset.

What happens when someone copies text?

They get the original text, without the generated line breaks, as plain text and as HTML with absolute links. Find-in-page, Text Fragment links and innerText do see a line break at each generated break; that is a known limitation, listed in SUPPORT.md.

Printing and translation tools?

Printed text wraps natively at the paper’s width; add --ts-break-display: inline to your print CSS to print the composition instead. When Google Translate, Chrome or Edge translates the page, Typeset removes its line breaks without touching the text the translator is filling in, and composes again when the page returns to its original language. The translator still receives each composed paragraph in pieces: in a live English to Spanish check, 3 of 9 test paragraphs got a stray space before punctuation, where the uncomposed page had none. No text is lost. Safari’s and Firefox’s translators are not detected and have not been tested. Reader views keep the composed lines: Firefox’s Reader View showed 35 of 40 test paragraphs composed at phone width as alternating long and short lines in its wider column; Safari Reader and Chrome’s Reading mode have not been measured. Details in SUPPORT.md.

Do I need to set the page language?

Yes, if you can: put lang="en" on your <html> tag for an English page. Typeset keeps “a”, “the” and “of” off line ends only in text declared English, and applies French, German and Spanish preferences the same way. Untagged text gets neutral preferences, which fix far fewer line ends: on 42 test paragraphs at 375 pixels, lines ending in a stranded short word went from 59 to 7 with lang="en", and only to 36 untagged. The script tag also curls quotes only in text declared English. Tags such as en_US or english are read as the language they name. Right-to-left and non-Latin scripts keep the browser’s layout.

Will it cause hydration errors on Next.js, Gatsby, Framer or Wix?

Not from 4.4 on pages it can recognise. If the script tag sets text before React takes over a server-rendered page, React reports a hydration error (#418) and renders the page again. On a page with a server-rendering framework’s marker (Next.js, Gatsby, Framer, Astro, Vue 2), the script now waits for the framework to hydrate before it sets anything, for at most 10 seconds. Wix pages carry no such marker: add data-typeset-defer="hydration" to the script tag. Neither Framer nor Wix has been checked on a live site yet. For text React renders, the React components avoid the question entirely; see the framework recipes.

Is it safe on comments and other text people post?

Keep it to text you publish. The script tag sets every matching element, so mark comment areas data-no-typeset or give the script a narrower selector, and set overflow-wrap: break-word on containers of user-generated text. From 4.4, a paragraph with more than 500 characters between two places a line may break, which prose never has but a hostile comment can, keeps the browser’s layout before anything is measured; in 4.3 such a run could freeze a Safari tab for tens of seconds.

What about readers without JavaScript?

They get your CSS. Set text-wrap: pretty on paragraphs and text-wrap: balance on headings, and they get the browser’s best.

When does it run, and can I defer it?

The pinned script tag runs after the page is parsed (defer), waits for web fonts and, on a server-rendered page, for the framework to hydrate, and composes what is on screen first, yielding between batches. To start later, install the npm package and call mount() when you choose, for example from requestIdleCallback. For screenshots and visual tests, wait for whenSettled(), which resolves once composition is done.

What does it cost?

About 5 milliseconds per paragraph on a fast laptop and about 21 at a mid-range phone’s speed, on the main thread. The first screen of a 200-paragraph article is done in about 55 milliseconds. Measured by npm run bench; the tables are in the repository’s docs/BENCHMARKS.md.

Does it phone home?

The script does not: no network requests, storage or telemetry, and no install scripts. The pinned file never changes, and its integrity hash makes the browser refuse it if it did. This website sets no cookies and runs no analytics; the privacy page lists what its host logs and which tools contact another site.

Something here wrong for your site? Report it, or see the framework recipes.