How to ligatures: from hot metal to opentype feature flags
- Step 1Start with the standard ligatures (liga) — `fi`, `fl`, `ffi`, `ffl` — the descendants of the hot-metal sorts. On by default. Disabling them reintroduces the `f`-`i` collision the ligature was invented to solve, so keep them on for body text in essentially all cases.
- Step 2Distinguish discretionary ligatures (dlig) — Optional, decorative pairs like `ct`, `st`, `sp`. Off by default — they read as ornamental in body copy. Opt in for display headlines and editorial serifs that were drawn with them. Many text fonts ship none at all.
- Step 3Understand contextual alternates (calt) — Not strictly a ligature, but bundled with them: context-aware glyph swaps. Powers signature-script joins, Arabic letter shaping, and subtle Latin alternates. On by default. The Ligature Toggler turns `calt` on/off alongside `liga`.
- Step 4Recognise historical ligatures (hlig) — The long-`s` (`ſ`) forms and other archaic combinations — `ſi`, `ſt`, `ſſ`. Off by default. Reserve for facsimiles of pre-1800 texts; in modern copy a long-`s` reads as a typo.
- Step 5Never disable required ligatures (rlig) — Script-essential ligatures the renderer must keep on for Arabic, Devanagari, and other complex scripts. They survive even the Toggler's 'off' row because the browser ignores attempts to disable them. Turning them off (where possible) breaks the script.
- Step 6See it for your own font — Drop a font into the [Ligature Toggler](/font-tools/ligature-toggler), type era-appropriate sample text, and compare the off / standard-on / discretionary-on rows. To enumerate which feature tags the font actually defines, run [opentype-features-inspector](/font-tools/opentype-features-inspector); to inspect the individual ligature glyphs themselves (outline, bounding box), use [glyph-inspector](/font-tools/glyph-inspector).
The five ligature-class OpenType tags
Feature descriptions match the registered-feature table in lib/font/font-processor.ts. 'Default' is the browser default; the Ligature Toggler can preview liga/calt/dlig directly.
| Tag | Name | Default | Intent | Disable safely? |
|---|---|---|---|---|
liga | Standard ligatures | On | Typographic hygiene — fuse fi/fl/ffi/ffl | Yes — for code/tabular/monogram contexts |
dlig | Discretionary ligatures | Off | Decorative craft — ct, st, sp in display faces | N/A — opt-in, off by default |
calt | Contextual alternates | On | Context-driven swaps — script joins, Arabic shaping | Rarely — can break script connections |
hlig | Historical ligatures | Off | Antiquarian forms — long-s (ſt, ſi) | N/A — opt-in, niche |
rlig | Required ligatures | On | Script survival — Arabic/Indic mandatory joins | No — disabling breaks complex scripts |
From hot metal to feature flag
The same problem (overhanging f, script-mandatory joins) solved by different technologies across five centuries.
| Era | Mechanism | Example | Modern equivalent |
|---|---|---|---|
| ~1450 (Gutenberg) | Cast a single combined metal sort | fi cut as one piece of lead | liga GSUB lookup |
| 1500s–1800s | Long-s sorts in everyday text | ſt, ſi in printed books | hlig (now opt-in) |
| Phototypesetting (1960s–80s) | Overlapping film masters | Display swash joins | dlig / calt |
| PostScript / TrueType (1980s–90s) | Limited built-in ligature glyphs | fi/fl in the cmap | Encoded glyphs, no feature flag |
| OpenType (2000s–now) | GSUB feature tags, switchable in CSS | font-feature-settings: "liga" 1 | What you toggle today |
Cookbook
Sample strings that surface each ligature class, and what you should expect to see across the Ligature Toggler's three rows. Drop your own font in to follow along.
The classic fi/ffl collision (liga)
ExampleThe original problem ligatures solve. Compare the off row (gap or overlap between f and i) with the standard-on row (clean fused glyph).
Sample text: office difficult fluffy off → f and i collide / leave an awkward gap standard → fi, ffi, ffl fuse cleanly (this is the point) dlig → unchanged (these are standard, not discretionary) Takeaway: keep liga on for body text. Always.
Decorative ct / st (dlig) in a display serif
ExampleDiscretionary ligatures are craft, not hygiene. They only appear in the dlig row, and only if the font drew them.
Sample text: Perfect Construction
standard → c-t and s-t render as separate letters
dlig → ct and st join with a connecting swash
(if absent, dlig row == standard row)
Where to use: headlines, editorial pull-quotes. Not body copy.Long-s historical ligatures (hlig)
ExampleThe hlig feature revives pre-1800 forms. The Toggler does not preview hlig directly — use it to confirm liga behaviour, then enable hlig by hand for facsimile work.
Modern: best most first
Historical: beſt moſt firſt (long-s + t/i ligatures)
CSS for a period-reproduction page:
.facsimile { font-feature-settings: "hlig" 1; }
(Only fonts with historical glyphs respond.)Why calt is bundled with liga
ExampleContextual alternates aren't ligatures, but they share the 'on by default, context-driven' character. The Toggler turns them on/off together.
Sample text (a script/handwriting font): hello world off (liga 0, calt 0) → letters stand apart, joins break on (liga 1, calt 1) → letters connect as the script intends Disabling calt on a connecting script breaks the joins — that's why the Toggler's 'off' row turns both off together.
Required ligatures survive the off row (rlig)
ExampleAn Arabic font shapes correctly even in the 'ligatures off' row, because rlig is not in the disable set and browsers keep it on regardless.
Sample text (Arabic font): لا (lam + alef) off row → still renders the mandatory لا ligature on row → same rlig is script survival, not decoration. You cannot, and should not, turn it off for complex-script content.
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Disabling liga in body text looks 'broken'
ExpectedWithout liga, the f overhang collides with a following i dot or l ascender — exactly the defect the ligature was invented to fix. This is why liga is on by default and why you should only disable it where 1:1 character mapping matters (code, terminals). For prose, off is the wrong choice.
Discretionary ligatures do nothing
By designMost text fonts ship no dlig glyphs — it is an opt-in display feature. Enabling dlig on a font without them is a harmless no-op, so the Toggler's third row matches the second. Confirm whether the font defines dlig with opentype-features-inspector.
Disabling calt breaks a script/handwriting font
Breaks renderingConnecting scripts (signatures, brush fonts, Arabic) rely on calt for letter joins. The Toggler's 'off' row turns calt off with liga, so a script font will visibly come apart in that row. This is a teaching artefact — in production, leave calt on for any connecting font.
Long-s (hlig) reads as a typo in modern copy
Expectedſ is correct in a 1700s facsimile and looks like a broken f everywhere else. hlig is off by default for good reason. Only enable it on explicitly historical content, and never on UI text — screen readers and search may also mishandle the long-s.
Required ligatures cannot be turned off
By designrlig is script-mandatory; browsers keep it on regardless of font-feature-settings. An Arabic lam-alef will render as a ligature even in the Toggler's 'off' row. This is intentional — disabling required ligatures would produce text that is simply wrong in that language.
The Toggler can't preview hlig/salt/ss01
Use sibling toolOnly liga, calt, and dlig appear in the three preview rows. To enumerate and preview the font's full feature set — hlig, salt, stylistic sets ss01–ss20 — use opentype-features-inspector, which lists every defined tag with ready CSS.
Ligatures interfere with text selection and search
ExpectedA fused ffi is one glyph but three characters; well-behaved browsers map selection/copy back to the underlying characters, so copy-paste yields ffi. Older PDF generators and some screen readers historically mis-handled this, which is one reason code displays disable ligatures entirely.
A monospace 'coding ligature' font is a different thing
ClarificationFira Code, JetBrains Mono and friends use calt/liga to turn => or != into a single arrow/glyph. Those are deliberate programming ligatures, not the fi/fl kind. Some developers love them, others disable them for exact character fidelity — the Toggler's 'off' row shows the raw, un-fused symbols.
Frequently asked questions
Why does the fi ligature exist at all?
Because the lowercase f has an overhang that collides with the dot of a following i (and the ascender of l). In hot-metal printing the fix was a single combined sort; in OpenType it is a liga GSUB lookup. The reason is purely visual — avoid the clash — and it has been the default behaviour for five centuries.
What's the difference between standard and discretionary ligatures?
Standard ligatures (liga) are typographic hygiene — fi/fl/ffi/ffl, on by default. Discretionary ligatures (dlig) are decorative craft — ct, st, sp in display faces, off by default. You keep liga everywhere and opt into dlig only for headlines and editorial typography.
Is calt a ligature?
Not strictly — calt (contextual alternates) substitutes glyphs based on surrounding context rather than fusing two into one. But it shares the same 'on by default, context-driven' character and is what makes connecting scripts join, so tools (including this one) often group it with ligatures.
When is it safe to disable ligatures?
Disable liga for code, terminals, tabular data, and monogram logos where each character must stand alone. Never disable rlig (breaks Arabic/Indic) and be careful with calt on connecting scripts. For body prose, leaving everything at the default is correct.
What is a long-s and why would I want it?
The long-s (ſ) is the archaic form of lowercase s used in the middle of words until ~1800, often ligated with a following t or i. You enable it (hlig) only for historical reproductions; in modern text it reads as a defective f.
Do all scripts use ligatures the same way?
No. In Latin they are mostly hygienic or decorative; in Arabic, Devanagari, and Bengali they are required for correct rendering and live under rlig/calt. Disabling Latin ligatures is cosmetic; disabling required ligatures in complex scripts produces text that is simply wrong.
Are programming ligatures (=>, !=) the same as fi ligatures?
Mechanically yes (both use GSUB/calt/liga), conceptually no. Fonts like Fira Code add deliberate symbol ligatures for operators; classic fi/fl ligatures are about letter shapes. Both are toggled with the same CSS, and developers often disable them for exact character fidelity.
How do I see which ligatures my font supports?
Preview liga/calt/dlig rendering directly in the Ligature Toggler. For a full inventory of feature tags the font defines (hlig, salt, ss01+), run opentype-features-inspector.
Does disabling a ligature change the font file?
No — it's pure CSS (font-feature-settings). The glyphs stay in the font; CSS just tells the renderer whether to apply the lookup. That's why the Ligature Toggler outputs CSS and an HTML preview rather than a modified binary.
Why do some PDFs show 'office' as 'office' when I search?
Because the PDF stored the ffi ligature as a single glyph without a proper ToUnicode mapping back to the three characters. Modern tooling avoids this, but it's a historical reason code/technical documents sometimes disable ligatures — to keep glyph and character one-to-one.
Should I prefer font-variant-ligatures over font-feature-settings?
For semantic clarity, yes — font-variant-ligatures: no-common-ligatures reads better than "liga" 0. The Ligature Toggler outputs font-feature-settings for maximum compatibility (~99%). See the features reference for the alias mapping.
Can I bake ligature choices into a design system?
Yes — pin the convention per component (body on, code off, headlines with dlig). The ligature CSS design-system guide lays out a documented set of defaults you can paste into your tokens. For variable display fonts, freeze the instance first with variable-font-freezer so the previewed ligatures match what you ship.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.