How to inspect every opentype feature in a font
- Step 1Drop your font on the tool — TTF, OTF, WOFF, or WOFF2. There are no options to set — the Inspector has a single upload zone and a Process button. WOFF and WOFF2 are decompressed to a flat sfnt in the browser before parsing, so all four formats behave identically.
- Step 2Press Process and read the metric chips — Three chips appear above the JSON: **Total features**, **GSUB**, and **GPOS**. A typical text font shows 5–15 total; a feature-rich display or programming font can show 40+. If Total features is 0, the font declares no `GSUB`/`GPOS` feature lists (common in bare icon fonts).
- Step 3Scan the features array — Each row is `{ tag, name, in_gsub, in_gpos, css_snippet }`. Sort is alphabetical by tag. `kern` and `mark`/`mkmk` usually show `in_gpos: true`; `liga`, `calt`, `smcp`, `ss01` show `in_gsub: true`. A tag can appear in both tables.
- Step 4Copy the CSS snippet you need — Grab the `css_snippet` for any feature: `font-feature-settings: "smcp" 1;`. To enable several at once, merge them into one declaration — `font-feature-settings: "smcp" 1, "ss01" 1, "tnum" 1;` — because a second `font-feature-settings` rule on the same selector overrides the first rather than adding to it.
- Step 5Verify the feature visually before shipping — The Inspector tells you a tag exists, not what it looks like. For ligatures, render before/after with [the ligature toggler](/font-tools/ligature-toggler). For kerning quality, audit pairs with [the kerning pair auditor](/font-tools/kerning-pair-auditor). For a specific glyph's outline, use [the glyph inspector](/font-tools/glyph-inspector).
- Step 6Save the JSON as a baseline — Download the `.features.json` file. Keep it in your repo so the next time the foundry ships an update you can diff the new feature list against this baseline and catch a dropped or renamed feature before it reaches production.
What the tool accepts and emits
The Inspector is a single-input, JSON-output analysis tool with no configurable options. Limits are per-job and come from the font-tools tier limits.
| Aspect | Behaviour | Notes |
|---|---|---|
| Input formats | TTF, OTF, WOFF, WOFF2 | WOFF (zlib) and WOFF2 (Brotli, via wawoff2 WASM) are decompressed to sfnt in-browser before parsing. A .zip, a variable-font .ttf, and a static font all parse the same way. |
| Options | None | There are no toggles, presets, or script selectors. You upload and press Process. |
| Output | JSON (downloadable + copyable) | { total_features, gsub_count, gpos_count, features: [...] }. Filename is <fontstem>.features.json. |
| Free tier limit | 5 MB per file, 1 file per job | Pro raises this to 50 MB; Developer to 1 GB. A 1,000-glyph cap applies to some font tools on free tier but feature inspection reads tables, not glyph outlines. |
| Privacy | Client-side only | Parsed with opentype.js in your browser. The result panel shows a "0 bytes uploaded" badge — the font never reaches a server. |
Reading the features array
Every object in the features array has exactly these fields. Examples below are typical for a well-built Latin text font.
| Field | Example | Meaning |
|---|---|---|
tag | smcp | The raw 4-character OpenType feature tag, as declared in the font's feature list. |
name | Small capitals | Human label from the built-in table of ~135 known tags. Unknown tags get (custom feature). |
in_gsub | true | The tag appears in the GSUB (substitution) feature list — it swaps glyphs (ligatures, small caps, stylistic sets). |
in_gpos | false | The tag appears in the GPOS (positioning) feature list — it moves glyphs (kerning, mark attachment, cursive joins). |
css_snippet | font-feature-settings: "smcp" 1; | Ready-to-paste CSS that activates this one feature. Combine multiple in a single declaration. |
Features you'll commonly see, by what they do
A reading guide for the most frequent tags in the Inspector's output. "Default" is the browser/shaping default when you set no font-feature-settings — fonts can't change this, the layout engine decides.
| Tag | Label (as shown) | Table | Browser default |
|---|---|---|---|
kern | Kerning | GPOS | On |
liga | Standard ligatures | GSUB | On |
calt | Contextual alternates | GSUB | On |
mark | Mark positioning | GPOS | On (for scripts that need it) |
smcp | Small capitals | GSUB | Off |
tnum | Tabular figures | GSUB | Off |
onum | Old-style figures | GSUB | Off |
ss01 | Stylistic set 1 | GSUB | Off |
swsh | Swash | GSUB | Off |
zero | Slashed zero | GSUB | Off |
Cookbook
Concrete reads of real Inspector output. The tool gives you the tag list; these recipes show how to turn that into shipping CSS and a verification plan. To go beyond "does this tag exist" into "what does it look like," pair with the ligature toggler and the glyph inspector.
Reading a typical text-font report
ExampleA well-rounded body font (think Inter, Source Sans) usually reports a compact set. Here the JSON tells you it has real small caps and tabular figures — so you don't need a separate caps font for UI labels.
{
"total_features": 9,
"gsub_count": 8,
"gpos_count": 1,
"features": [
{ "tag": "calt", "name": "Contextual alternates", "in_gsub": true, "in_gpos": false, "css_snippet": "font-feature-settings: \"calt\" 1;" },
{ "tag": "frac", "name": "Fractions", "in_gsub": true, "in_gpos": false, "css_snippet": "font-feature-settings: \"frac\" 1;" },
{ "tag": "kern", "name": "Kerning", "in_gsub": false, "in_gpos": true, "css_snippet": "font-feature-settings: \"kern\" 1;" },
{ "tag": "liga", "name": "Standard ligatures", "in_gsub": true, "in_gpos": false, "css_snippet": "font-feature-settings: \"liga\" 1;" },
{ "tag": "smcp", "name": "Small capitals", "in_gsub": true, "in_gpos": false, "css_snippet": "font-feature-settings: \"smcp\" 1;" },
{ "tag": "tnum", "name": "Tabular figures", "in_gsub": true, "in_gpos": false, "css_snippet": "font-feature-settings: \"tnum\" 1;" }
]
}Enabling several features in one declaration
ExampleThe css_snippet field gives one feature per line. Don't paste them as separate font-feature-settings rules — the later one wins. Merge them.
/* WRONG — second rule overrides the first, so only tnum applies */
.price { font-feature-settings: "smcp" 1; }
.price { font-feature-settings: "tnum" 1; }
/* RIGHT — one declaration, comma-separated */
.price { font-feature-settings: "smcp" 1, "tnum" 1; }Confirming a slashed zero before using it for code
ExampleDesigners ask for a slashed zero so 0 and O don't collide. The tag is zero. Inspect first — many text fonts don't ship it, and the CSS silently no-ops if the feature isn't there.
Inspector output contains:
{ "tag": "zero", "name": "Slashed zero", "in_gsub": true, ... }
Then in CSS:
code, .tabular { font-feature-settings: "zero" 1, "tnum" 1; }
If `zero` is absent from the report, the font has no slashed-zero
glyph — the declaration does nothing and you keep the dotted/plain 0.Counting stylistic sets
ExampleStylistic sets are tags ss01 through ss20. The Inspector lists each present set as its own row with label 'Stylistic set N'. Count them to know how much design flexibility the font ships.
grep-style read of the features array: ss01 Stylistic set 1 ss02 Stylistic set 2 ss03 Stylistic set 3 ss04 Stylistic set 4 → four stylistic sets available. What each one *does* is font-defined — the Inspector names the slot, not the effect. Render each to see it (see the stylistic-sets guide).
Using high-level font-variant aliases instead
ExampleFor common features, CSS has readable aliases that resolve to the same feature. Prefer them where one exists; fall back to font-feature-settings for tags with no alias (ss01, swsh, frac context, etc.).
/* Equivalent pairs */ font-variant-caps: small-caps; /* ≈ "smcp" 1 */ font-variant-numeric: tabular-nums; /* ≈ "tnum" 1 */ font-variant-numeric: oldstyle-nums; /* ≈ "onum" 1 */ font-variant-ligatures: discretionary-ligatures; /* ≈ "dlig" 1 */ /* No alias — use the low-level escape hatch */ font-feature-settings: "ss03" 1, "swsh" 1;
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.
Font reports zero features
ExpectedMany fonts — bare icon fonts, some webfont subsets, older TTFs — ship no GSUB or GPOS feature lists at all. The Inspector returns total_features: 0 with an empty array. This is not an error: the font simply has no declared OpenType features. kern data may still exist in a legacy kern table (not GPOS), which this tool does not surface — check the kerning pair auditor if kerning matters.
Unknown 4-character tag in the font
PreservedIf a font declares a tag that isn't in the built-in label table of ~135 entries — a private feature, a freshly-registered tag, or a non-standard one — the Inspector keeps it and labels it (custom feature). The tag, in_gsub/in_gpos flags, and css_snippet are all still emitted, so you can still enable it; you just don't get a friendly name.
Tabular and old-style figures both present
SupportedA font can declare tnum, pnum, lnum, and onum all at once — tabular vs proportional crossed with lining vs old-style. The Inspector lists each as a separate row. The font ships all four glyph styles; which one renders depends on the font-feature-settings / font-variant-numeric you set, and the browser default (usually proportional lining). Picking conflicting pairs (tnum + pnum) is up to the shaping engine to resolve.
Feature exists but the matching glyphs don't
By designA feature tag in GSUB only means the font declares the feature — not that every character has an alternate. smcp might cover Latin but not Cyrillic; frac might handle common fractions only. The Inspector reports the tag's presence, not its coverage. To check which characters a font supports at all, use the character coverage map.
Same tag appears in both GSUB and GPOS
SupportedA few tags (notably kern in some fonts, or contextual features) can be declared in both tables. The Inspector sets both in_gsub and in_gpos to true on that single row rather than duplicating it. The total_features count is the size of the deduplicated union of both tables, so it never double-counts.
WOFF2 file that fails to decompress
ErrorWOFF2 is decompressed with the wawoff2 WASM module before parsing. If the file is a truncated, corrupt, or mislabeled WOFF2, decompression throws and the tool shows an error rather than a feature list. Re-download the font, or convert it to TTF first with [a WOFF2-to-TTF tool] and re-inspect. The same applies to a WOFF whose internal zlib streams are damaged.
Variable font
SupportedA variable font's GSUB/GPOS feature lists are inspected exactly like a static font's — the Inspector reports the same feature tags regardless of axis position. Note the tool does not report variation axes (wght, wdth, opsz); those live in fvar, not the feature tables. Some variable fonts add rvrn (Required variation alternates) — you'll see it listed if present.
Non-font file uploaded
InvalidUpload a PDF, image, or renamed .ttf that isn't actually a font and format detection rejects it before parsing with "Unsupported font format" or "not a supported font (TTF/OTF/WOFF/WOFF2)". The Inspector never silently returns an empty list for a non-font — you get an explicit error.
File over the tier size limit
413-style rejectFree tier caps each font at 5 MB. A large CJK font with tens of thousands of glyphs can exceed that; the tool blocks the job with a message naming the limit and suggesting an upgrade (Pro 50 MB, Developer 1 GB). The feature tables themselves are small, but the whole file must load to reach them.
Frequently asked questions
Which OpenType features are most useful to look for?
For body text: kern (kerning, default on), liga (standard ligatures, default on), calt (contextual alternates, default on). For polish: smcp (small caps), tnum (tabular figures for tables), onum (old-style figures for running text), frac (fractions), zero (slashed zero for code). For display: ss01–ss20 (stylistic sets), swsh (swashes), salt (stylistic alternates), titl (titling). The Inspector lists whichever of these your specific font actually declares.
Can the tool show me what each stylistic set actually does?
No — and no tool can do it from the tables alone. Stylistic sets (ss01–ss20) are font-defined: ss01 in one font alters the dollar sign, in another it changes the a. The Inspector names the slot (Stylistic set 1) and confirms it exists. To see the visual effect you have to render it; the foundry's specimen is the authoritative description of intent.
Why is `kern` shown under GPOS, not GSUB?
GSUB features substitute one glyph for another (ligatures, small caps). GPOS features adjust positions without changing glyphs (kerning, mark attachment). Kerning is a positioning operation, so modern fonts declare it in GPOS. Some legacy fonts instead carry kerning in an old kern table that this tool does not read — if kern is missing from the report but the font clearly kerns, that's why.
The font's specimen lists features the tool doesn't report. Why?
Three common reasons: (1) the specimen describes a different cut/weight than the file you inspected; (2) the feature is in a legacy table the tool doesn't read (old kern); (3) the marketing copy is aspirational. The Inspector reports only what the uploaded file's GSUB/GPOS feature lists actually declare — that's the ground truth for what font-feature-settings can activate.
What input formats does it accept?
TTF, OTF, WOFF, and WOFF2. WOFF (zlib-compressed) and WOFF2 (Brotli-compressed) are decompressed to a flat sfnt in your browser before parsing, so all four behave the same. There are no other options — it's a single upload zone and a Process button.
How do I enable a feature once I've found it?
Copy the css_snippet field, e.g. font-feature-settings: "smcp" 1;. To enable several, merge them into one declaration: font-feature-settings: "smcp" 1, "tnum" 1;. Don't write two separate font-feature-settings rules on the same selector — the second silently overrides the first. Where a font-variant-* alias exists (font-variant-caps: small-caps), it's more readable.
How are features different from variation axes?
Features are discrete on/off substitutions and positionings (small caps, ligatures, tabular figures) declared in GSUB/GPOS. Variation axes are continuous ranges (weight 100–900, width, optical size) declared in fvar. They're orthogonal — a variable font can have both, and you control them with two different CSS properties (font-feature-settings vs font-variation-settings). This tool reports features; to bake an axis value into a static file use the variable font freezer.
Does the Inspector tell me which script a feature belongs to?
No. A feature can be scoped per script/language in the font (Arabic ligatures only under arab, Cyrillic localised forms under locl), but the Inspector reports the deduplicated union of feature tags across the whole GSUB/GPOS table. It tells you the tag is present somewhere in the font, not the script subtable it's wired to.
Is my font uploaded to a server?
No. Parsing runs in your browser with opentype.js (plus a WASM decompressor for WOFF2). The result panel shows a "0 bytes uploaded" badge. This is why you can safely inspect unreleased, licensed, or NDA fonts — the bytes never leave your machine.
What does `total_features` actually count?
The size of the deduplicated union of the GSUB and GPOS feature tag sets. If liga is only in GSUB and kern only in GPOS, both count once. If a tag is in both tables, it still counts once (and its row has both in_gsub and in_gpos true). gsub_count and gpos_count are the individual table sizes and can sum to more than total_features when tags overlap.
Can I diff two versions of a font?
Yes — that's a strong use case. Download the .features.json for the old version and the new one, then run any JSON or text diff. A removed tag (the foundry dropped dlig) or an added one shows up immediately. For automating that diff in CI, see the developer/automation companion guide for this tool.
Can I run this from the command line or a script?
JAD's hosted API never accepts uploads. GET /api/v1/tools/opentype-features-inspector returns the tool schema, and execution happens through a paired @jadapps/runner on your own machine: POST the payload to http://127.0.0.1:9789/v1/tools/opentype-features-inspector/run. For a Node-only approach you can also call opentype.js directly — the developer guide for this tool shows a script that walks a fonts directory.
Which related tools should I reach for next?
After confirming features exist: ligature toggler to render ligatures on/off, kerning pair auditor for kerning quality, glyph inspector for a specific glyph's outline and metrics, character coverage map for which characters the font supports, and font metadata extractor for name/version/license data.
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.