Polyfill/extension for CSS text-wrap: pretty to bias against breaks after minor words and apply safe typographic joins.
npm install text-wrap-minor-words

Experimental, CSS-first polyfill that augments text-wrap: pretty by biasing against line breaks immediately after minor words (articles, prepositions, short conjunctions) in languages where this is a widely accepted typesetting convention. It also applies a couple of safe, language-agnostic joins (e.g., Fig. 2, 20 °C).
Status: experimental. See explainer.md. Live demo: https://jlorenzetti.github.io/text-wrap-minor-words/
text-wrap: pretty improves paragraph breaking but does not let authors express locale-aware preferences about breaking immediately after minor words. Many European languages treat this as a common editorial convention even in body text. This library offers a CSS-first polyfill so authors can experiment today and help inform standardization.
``bash`
npm i text-wrap-minor-words
`ts
import { init } from 'text-wrap-minor-words';
// Process elements that compute to text-wrap: pretty
const ctrl = init({ observe: true });
// Optionally process a specific subtree later:
// ctrl.process(element);
`
HTML markup should declare the language (lang) on blocks:
` Vado a casa con la bici. Je vais à Paris. Jestem w domu i czekam. See Fig. 2 for details. It was 20 °C at 9:30 am.html`
A display heading if you want to opt-in later
`html`
- Extends text-wrap: pretty behavior by inserting NBSP after minor words in languages where this is customary (Romance, Slavic, Greek by default).Fig. 2
- Applies safe joins regardless of language:
- label + number: , p. 12, § 520 °C
- number + unit: , 9:30 amMr. Smith
- honorific + Name: , Dr. MüllerJ. K. Rowling
- initials sequence:
- numeric ranges: adds WORD JOINER around the dash
Load only the core engine and register the locales you actually use. This keeps bundles small and avoids shipping unnecessary language data.
`ts
// Load the lite entry (no locales included)
import { init, registerLanguage } from 'text-wrap-minor-words/lite';
// Register only the locales you need (example: Italian)
import it from 'text-wrap-minor-words/locales/it.json';
registerLanguage('it', it);
// Optionally preload the same tags here (helps the engine avoid a first lookup)
init({ languages: ['it'] });
`
Browser global (lite):
`html`
Notes:
- The default (non‑lite) entry includes built‑in locale data for quick trials. Prefer the lite entry in production apps.
- You can register multiple locales by calling registerLanguage(tag, data) more than once.
Advanced (CSS opt‑in per container):
You can enable the minor‑words preference declaratively on specific elements via CSS. This is useful for safe‑only languages where you want the behavior only in display contexts.
`css`
h1[lang="en"], h2[lang="en"] {
--text-wrap-preferences: minor-words;
--text-wrap-minor-threshold: 1; / glue after 1‑letter tokens /
--text-wrap-minor-stoplist: "of to in on at for by a I"; / optional additions /
}
These custom properties are read when the preference is opted‑in on the element (or an ancestor) and the current language doesn’t have a built‑in minor‑words configuration.
- Active by default: be, bg, ca, cs, el, es, fr, gl, hr, it, mk, pl, pt, ro, ru, sk, sl, sr, uk.
- Neutral by default: da, de, en, lt, lv, nb, nl, nn, sv (only safe joins; no minor-words glue in body text).
- The effective language is taken from lang (with fallback to the document root).
`ts`
type InitOptions = {
selector?: string; // default: 'html' (scans under elements that compute to text-wrap: pretty)
languages?: string[]; // pre-load specific BCP-47 primary subtags (e.g., ['it','en'])
observe?: boolean; // MutationObserver to process added/edited content
context?: 'all'|'display'; // if 'display', only process headings/DT
};
Returns a controller { process(root?), disconnect() }.
- The library reads lang to select language defaults.en
- Neutral languages (e.g., , de, nl) do not enable minor-words glue by default; only safe joins apply.{ context: 'display' }
- For display-only processing, pass .languages: ['it','fr']
- You can pre-load language data via to avoid first-use compile cost.
CSS preference gate and overrides:
- You can opt in/out declaratively per container with --text-wrap-preferences: minor-words | none. On browsers without text-wrap: pretty, authors can set the preference under @supports not (text-wrap: pretty).minorWords
- When the preference is active and the current language has no built‑in , the engine reads optional overrides:--text-wrap-minor-threshold:
- (glue after tokens up to N chars; typical value: 1)--text-wrap-minor-stoplist: "space-separated tokens"
- (per‑container additions)
Example (headings in English):
`css`
h1[lang="en"], h2[lang="en"] {
--text-wrap-preferences: minor-words;
--text-wrap-minor-threshold: 1;
--text-wrap-minor-stoplist: "of to in on at for by a I";
}
- One TreeWalker pass over text nodes under elements that compute to text-wrap: pretty.pre, code, kbd, samp, script, style, textarea, math, svg, [contenteditable]
- No layout measurements; O(n) string replacements; NBSP insertions are idempotent.
- Skips and basic URL/email-like text.
- Does not cross inline elements by default.
- The polyfill acts only where the computed style is text-wrap: pretty. On browsers without support, it effectively no-ops unless the author explicitly applies an opt-in selector targeting the same blocks.
- The library itself targets modern evergreen browsers (ES2020, Intl.Segmenter optional).
- Language tables live in src/data/languages/.minorWords
- To propose additions:
1. Add or edit the JSON with (threshold + list) and lexical categories (labels, honorifics, abbrCompounds).tests/engine.spec.ts
2. Add unit tests in (or a new spec file) with input → expected output.npm run test
3. Run .
- Run tests: npm run testnpm run bench
- Run a simple throughput benchmark:
- No apostrophe/elision handling (out of scope for now).
- Does not measure layout; it applies static glues consistent with editorial conventions.
- Does not cross inline elements unless an advanced option is introduced in future.
- This repo accompanies the explainer (explainer.md) that proposes text-wrap-preferences: minor-words as an additive, language-sensitive author preference for paragraph-aware wrapping.docs/LEXICON.md`.
For a consolidated list of safe labels/honorifics used by the polyfill, see
MIT