How to toggle opentype ligatures and preview the result
- Step 1Upload the font — Drop a TTF, OTF, WOFF, or WOFF2 onto the upload zone, or click to browse. Free tier accepts files up to **5 MB**; Pro raises that to 50 MB and Developer to 1 GB. One file at a time — this is a single-font preview, so process each weight or family member separately.
- Step 2Type sample text that exercises ligatures — The default `office difficult fluffy` covers `fi`, `ffi`, and `ffl`. Replace it with your real headline or brand name — e.g. `Affinity Office` for `ff` + `fi`, or `first flight` to test `fi` + `fl`. There is one textarea; whatever you type is the exact string rendered in all three rows.
- Step 3Click Process — The tool inlines the font as a base64 data URI inside an `@font-face` (family `LigaToggle_<stem>`, `font-display: block`) and builds the three-row HTML. On paid tiers with the runner paired, the job auto-routes to your local machine; otherwise it runs in the browser tab.
- Step 4Read the three rows top to bottom — Row 1 = **ligatures off** (`liga 0, calt 0, dlig 0`) — every glyph stands alone. Row 2 = **standard on** (`liga 1, calt 1, dlig 0`) — the browser default; `fi`/`ffl` fuse. Row 3 = **discretionary on** (`liga 1, calt 1, dlig 1`) — only differs from row 2 if the font actually contains `dlig` glyphs (most text fonts do not).
- Step 5Pick the row you want and copy the CSS — Below the preview is a `<pre>` block with two snippets: `body { font-feature-settings: "liga" 0, "calt" 0; }` to disable site-wide, and `h1, h2 { font-feature-settings: "liga" 1, "calt" 1, "dlig" 1; }` to turn on discretionary ligatures for headlines. The Copy button copies the whole HTML (font + CSS); the Download button saves the `.html` file.
- Step 6Scope the CSS to the right selector in your stylesheet — `font-feature-settings` inherits, so set it on `body` for the global default and override on `code, pre` (off for 1:1 character mapping) or `h1, h2` (on with `dlig`). For a documented per-component convention, see the [ligature CSS design-system guide](/font-tools/guides/ligature-css-design-system).
What each preview row actually sets
The three rows are fixed in the generated HTML — there are no checkboxes to change them. These are the exact font-feature-settings strings the tool emits, verified against the handler in lib/font/font-processor.ts.
| Preview row | font-feature-settings | What it shows | When you want it |
|---|---|---|---|
| Ligatures off | "liga" 0, "calt" 0, "dlig" 0 | Every character renders as its own standalone glyph — f and i keep their gap, no contextual swaps | Code/terminal displays, tabular data, anywhere each character must be individually addressable |
| Standard ligatures on (default) | "liga" 1, "calt" 1, "dlig" 0 | What every browser does out of the box: fi, fl, ffi, ffl fuse; contextual alternates fire | Body text, 99% of running prose — this is the baseline you usually keep |
| Discretionary ligatures on (rare) | "liga" 1, "calt" 1, "dlig" 1 | Adds decorative pairs (ct, st, sp) — only visibly different from row 2 if the font ships dlig glyphs | Display headlines and editorial serifs that were drawn with discretionary ligatures |
Accepted inputs and tier limits
Real numbers from lib/font/font-utils.ts (FREE/PRO/DEVELOPER_FONT_FILE_LIMIT_BYTES). One font per run; output is always an HTML preview.
| Property | Value | Notes |
|---|---|---|
| Input formats | TTF, OTF, WOFF, WOFF2 | Any web/desktop font format opentype.js can read for the @font-face inline |
| Free tier max size | 5 MB | FREE_FONT_FILE_LIMIT_BYTES = 5_000_000 — covers virtually every single-weight webfont |
| Pro tier max size | 50 MB | Large CJK or icon fonts |
| Developer tier max size | 1 GB | Practical ceiling is browser memory, not the limit |
| Files per run | 1 | Single-font preview; use font-comparison-overlay for A/B |
| Output | Self-contained HTML | Filename <stem>.ligatures.html, MIME text/html, font base64-inlined |
| Only adjustable option | Sample text | One textarea — no feature toggles, presets, or font-size control in the UI |
Cookbook
Concrete sample-text choices and the CSS each preview row maps to. The 'preview' lines describe what you see in the rendered HTML; the 'CSS to ship' lines are what you paste into your stylesheet.
Confirm fi/ffl fuse in your body font
ExampleThe default sample exercises the three most common Latin ligatures. Row 2 (standard on) is your real-world body-text rendering.
Sample text: office difficult fluffy
Preview rows:
off o f f i c e d i f f i c u l t f l u f f y (gaps visible)
on (def) o[ffi]ce di[ffi]cult flu[ffy] (fused)
dlig same as 'on' unless the font has dlig glyphs
CSS to ship (keep default — nothing to do):
body { font-feature-settings: "liga" 1, "calt" 1; }Disable ligatures for a code/monospace block
ExampleIn code, fi fusing breaks character-by-character alignment and copy-paste fidelity. Pick the top row and ship the disable snippet.
Sample text: def fix_offset(i):
Preview row to match: 'Ligatures off' (row 1)
CSS to ship (from the generated <pre>):
code, pre {
font-feature-settings: "liga" 0, "calt" 0;
}Turn on discretionary ligatures for a display serif
ExampleEditorial serifs (e.g. a Garamond or Caslon revival) often draw ct and st discretionary ligatures. Row 3 shows whether yours has them.
Sample text: Perfect Construction
Preview rows:
on (default) ... ct and st render as separate glyphs
dlig ... ct and st join with the decorative swash
CSS to ship (headlines only):
h1, h2 {
font-feature-settings: "liga" 1, "calt" 1, "dlig" 1;
}Test a brand wordmark for unwanted ligature fusion
ExampleSome logos look wrong when an fi or ff fuses. Render the wordmark and decide per-selector — turn liga off just on the logo element.
Sample text: Affinity
If the 'ffi' fusion in row 2 looks off-brand, ship:
.logo { font-feature-settings: "liga" 0; }
Leave the rest of the page at the default (liga on).Open the downloaded HTML offline to share with a designer
ExampleThe download is fully self-contained — the font is base64-inlined, so the file renders the same on any machine with no font installed.
1. Process the font, click Download. 2. File saved: MyFont.ligatures.html 3. Email it / drop it in Slack. 4. Recipient double-clicks → opens in any browser, sees the three rows rendered in MyFont, no install needed. (The font travels inside the file as a base64 data URI.)
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.
Row 3 (dlig) looks identical to row 2
By designMost text fonts ship no dlig glyphs at all — dlig is a discretionary, opt-in feature usually found only in display and editorial faces. When the font has no dlig lookups, font-feature-settings: "dlig" 1 is a harmless no-op and rows 2 and 3 render the same. That is correct behaviour, not a bug. To confirm whether the font defines dlig, run opentype-features-inspector.
No font uploaded
ErrorThe handler throws Upload a font file. if you click Process with no file selected. There is no demo font baked in — the preview always renders your uploaded font, so a file is required.
File larger than the tier limit
RejectedFiles over the per-job size limit (5 MB free, 50 MB Pro, 1 GB Developer) are blocked before processing with a message naming the tier and limit. A typical single-weight WOFF2 is 15–80 KB, so this only bites huge CJK or multi-master desktop fonts on the free tier.
Non-font file (e.g. a .zip or .png renamed)
RejectedInputs are validated by isSupportedFontFile; anything that is not TTF/OTF/WOFF/WOFF2 is rejected with not a supported font. The tool never tries to parse arbitrary bytes as a font.
The binary's GSUB ligatures are not modified
By designThis tool emits CSS only — it does not rewrite the font's GSUB table. opentype.js can read OpenType layout tables but does not reliably write them, so editing the binary would risk corruption. To remove a feature for real you would need desktop tooling (fonttools); for the web, CSS toggling is the safe, reversible answer.
calt is bundled into the on/off rows, not isolated
ExpectedThe preview groups calt with liga: the off row sets both to 0 and the on rows set both to 1. There is no row that shows liga 1, calt 0. If you need to isolate contextual alternates (common in script and signature fonts), write that combination by hand and verify against the live page — this tool's rows are fixed.
Required ligatures (rlig) still render even in the 'off' row
ExpectedThe 'off' row disables liga, calt, and dlig but not rlig. Required ligatures are script-essential (Arabic, Devanagari) and the browser keeps them on regardless. So an Arabic or Indic font will still shape correctly in row 1 — disabling Latin ligatures does not break complex-script integrity.
font-display: block causes a brief invisible flash on slow render
ExpectedThe inlined @font-face uses font-display: block, so the preview text is invisible for a few hundred ms while the base64 font decodes, then snaps in. This is intentional for an accurate preview (no fallback-font flash), and the font is inline so the delay is negligible. It does not reflect what your production font-display will be.
Very long sample text overflows the preview panel
PreservedThe in-page preview panel scrolls (max height ~125 in the result container); long strings wrap or scroll rather than being truncated. The downloaded HTML has no such cap and renders the full string. Your exact text is HTML-escaped and preserved verbatim.
Frequently asked questions
Does this tool change my font file?
No. It produces an HTML preview plus CSS — the font binary is never modified or re-serialised. Toggling ligatures via font-feature-settings is a CSS-side, reversible operation, which is why it can never corrupt the file. If you genuinely need the feature stripped from the binary, that requires desktop tooling (fonttools), not this tool.
Why three fixed rows instead of checkboxes for each feature?
The handler emits exactly three rows — off, standard-on (default), discretionary-on — and the only UI control is the sample-text box. The fixed layout keeps the comparison consistent and matches the three states almost everyone actually needs. To explore an arbitrary feature combination, copy the generated CSS and edit the font-feature-settings string by hand.
What file do I get out?
A single self-contained HTML file named <stem>.ligatures.html (MIME text/html) with the font base64-inlined as an @font-face. It renders the three rows and includes a <pre> of copy-paste CSS. You can download it or use the Copy button to grab the full HTML.
What's the difference between liga and calt?
liga is standard ligatures — the fi/fl/ffl fusions everyone knows. calt is contextual alternates — context-aware substitutions like Arabic letter shaping or signature-script joins. Both are on by default in browsers, and this tool turns them on/off together in the preview.
When should I disable ligatures?
Code and terminal displays (each character must map 1:1), tabular numeric data, monogram-style logos where a fused ff looks wrong, and any app that addresses characters individually. For running prose, leave them on — disabling liga reintroduces the awkward f-i collision the ligature was designed to fix.
Can I preview hlig, salt, or ss01 with this tool?
Not in the rendered rows — the preview only shows liga, calt, and dlig. To see which features a font actually defines (hlig, salt, ss01–ss20, etc.) and get ready-made CSS for each, use opentype-features-inspector. Then add the relevant font-feature-settings line yourself.
Why does turning on discretionary ligatures do nothing for my font?
Because the font has no dlig glyphs. dlig is opt-in and only present in fonts whose designer drew decorative pairs (ct, st). Setting "dlig" 1 on a font without them is a no-op — rows 2 and 3 render identically. Inspect the feature list with the opentype-features-inspector to confirm.
Is my font uploaded to a server?
No. Processing happens in your browser tab (or your own machine via the local runner on paid tiers), and the result panel shows a 0 bytes uploaded badge. Unreleased brand fonts and licensed foundry files never leave your control.
Can I change the preview font size?
Not from the UI — the preview is fixed at 32px in the generated CSS, and the only adjustable input is the sample text. If you need a different size, download the HTML and edit the .lig-preview { font-size } rule, or paste the font-feature-settings into your own test page.
Should I use font-feature-settings or font-variant-ligatures?
font-variant-ligatures (e.g. common-ligatures / no-common-ligatures) is the cleaner high-level alias with ~97% support; font-feature-settings is the low-level form (~99% support) this tool outputs. For most sites either works; use font-feature-settings when you need maximum compatibility or finer control. See the features reference.
Does it work on variable fonts?
Yes. The font is inlined as-is for the preview, and feature lookups are independent of variation axes, so ligatures render at the font's default instance. If you need to freeze a specific axis first, run variable-font-freezer and preview the result.
Can I automate this for a whole font folder?
Yes, on paid tiers. GET /api/v1/tools/ligature-toggler returns the (single-option) schema; pair the @jadapps/runner once and POST { sampleText } plus the font to 127.0.0.1:9789/v1/tools/ligature-toggler/run to get the HTML back locally. See the build-time generation guide.
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.