Professional fonts ship with dozens of alternate glyphs and typographic behaviors hidden behind OpenType feature tags. Inter includes contextual alternates and tabular figures. Source Sans Pro has small caps, old-style figures, and fractions. Hoefler Text has swashes, ornaments, and seven stylistic sets. These features exist in the font file — already downloaded, already paid for — but render only when explicitly activated.
Most web developers never touch them because the activation mechanism is confusing: four-character tags, two competing CSS syntaxes, and poor discoverability. This guide covers every practical OpenType feature, how to activate each one, and how to discover what features your fonts actually support.
Two CSS Syntaxes: font-variant vs font-feature-settings
CSS provides two ways to activate OpenType features. They control the same underlying font behavior but with different syntax and cascading characteristics.
font-variant-* (Preferred)
The font-variant-* properties are the high-level, human-readable syntax:
/* Individual properties */
body {
font-variant-ligatures: common-ligatures;
font-variant-numeric: oldstyle-nums tabular-nums;
font-variant-caps: small-caps;
}
/* Shorthand */
body {
font-variant: common-ligatures oldstyle-nums small-caps;
}Advantages: readable, cascades independently (setting font-variant-caps on a child doesn't reset font-variant-ligatures from the parent), and maps to standard CSS concepts. Use this syntax as your default.
font-feature-settings (Low-Level)
body {
font-feature-settings: "liga" 1, "onum" 1, "smcp" 1, "tnum" 1;
}The low-level syntax uses four-character OpenType tags directly. Each tag is followed by 1 (on), 0 (off), or a numeric value for parametric features.
Critical problem: font-feature-settings doesn't cascade per-tag. If a parent sets font-feature-settings: "liga" 1, "onum" 1 and a child overrides with font-feature-settings: "smcp" 1, the child loses both liga and onum. You must redeclare every tag on every element that overrides any tag. This is the #1 source of OpenType bugs in CSS.
Use font-feature-settings only for features that font-variant-* doesn't cover — primarily stylistic sets (ss01-ss20), swashes (swsh), and other discretionary features.
Ligatures (liga, dlig, clig)
Ligatures merge two or more characters into a single glyph to improve readability or aesthetics. The classic example: "fi" as a ligature merges the dot of the 'i' into the curve of the 'f', preventing a visual collision.
Types of Ligatures
| Tag | CSS | Description | Default |
|---|---|---|---|
liga | font-variant-ligatures: common-ligatures | Standard ligatures: fi, fl, ffi, ffl | On |
clig | font-variant-ligatures: contextual | Contextual alternates based on surrounding characters | On |
dlig | font-variant-ligatures: discretionary-ligatures | Decorative ligatures: ct, st, Th | Off |
hlig | font-variant-ligatures: historical-ligatures | Historical forms: long s (s), ct ligature | Off |
Standard ligatures (liga) are on by default in all browsers. You never need to enable them — they just work if the font includes them. Discretionary ligatures (dlig) are off by default because they're decorative and may not suit all contexts.
When to Disable Ligatures
Ligatures should be disabled in code and UI elements where character identity matters:
/* Disable in code blocks — ligatures confuse code reading */
pre, code, .monospace {
font-variant-ligatures: none;
/* or font-feature-settings: "liga" 0, "clig" 0; */
}Code fonts like Fira Code intentionally use ligatures for operators (=>, !==, >=) — but these are a deliberate design choice, not inherited from the body font. If your body font's ligatures leak into code blocks via CSS inheritance, disable them explicitly.
Kerning (kern)
Kerning adjusts the spacing between specific character pairs. Without kerning, "AV" and "To" have awkward gaps because the default spacing doesn't account for how these letters' shapes fit together.
/* Kerning is on by default, but you can be explicit */
body {
font-kerning: normal; /* on (default) */
}
/* Disable kerning (rare) */
.no-kern {
font-kerning: none;
}Kerning is enabled by default in all modern browsers. The only reason to disable it is performance on very long text (thousands of characters) where kerning calculations add measurable rendering time — an edge case for most sites.
Small Caps (smcp, c2sc)
Small caps are uppercase letterforms scaled to x-height — shorter than full capitals but with the same proportions and stroke weight. They're used for abbreviations (NASA, HTML), legal text, and stylistic emphasis.
/* True small caps from the font */
.small-caps {
font-variant-caps: small-caps;
}
/* All small caps (both upper and lowercase become small caps) */
.all-small-caps {
font-variant-caps: all-small-caps;
}
/* Petite caps (even smaller, if the font supports it) */
.petite-caps {
font-variant-caps: petite-caps;
}
True vs Faux Small Caps
True small caps (from OpenType smcp feature) are specifically designed glyphs with correct stroke weight and proportions. Faux small caps (from CSS font-variant: small-caps without a supporting font) are simply shrunken capitals — they look too thin compared to surrounding text because their strokes are scaled down.
How to tell the difference: true small caps have visually consistent stroke weight with the regular lowercase text. Faux small caps look noticeably lighter. If your font doesn't include the smcp feature, the browser synthesizes faux small caps. Check your font's features before relying on font-variant-caps.
Numeric Features: Figures and Fractions
Fonts can contain multiple sets of number designs. The four main options are controlled by two independent axes: style (lining vs old-style) and spacing (proportional vs tabular).
Figure Styles
| Feature | Tag | CSS | Description |
|---|---|---|---|
| Lining figures | lnum | font-variant-numeric: lining-nums | Numbers sit on the baseline and have uniform height (like capitals). Default for most fonts |
| Old-style figures | onum | font-variant-numeric: oldstyle-nums | Numbers have ascenders and descenders like lowercase letters. 3, 4, 5, 7, 9 dip below baseline |
| Tabular figures | tnum | font-variant-numeric: tabular-nums | All numbers have the same width. Columns align vertically |
| Proportional figures | pnum | font-variant-numeric: proportional-nums | Numbers have natural widths. "1" is narrower than "8" |
Combine style and spacing independently:
/* Old-style, proportional — best for body text */
p {
font-variant-numeric: oldstyle-nums proportional-nums;
}
/* Lining, tabular — best for tables and data */
table {
font-variant-numeric: lining-nums tabular-nums;
}
/* Tabular for prices (numbers align in lists) */
.price {
font-variant-numeric: tabular-nums;
}
Fractions (frac)
/* Activate proper fraction rendering */
.fraction {
font-variant-numeric: diagonal-fractions;
}
/* 1/2 renders as a proper fraction glyph (numerator over denominator) */
The frac feature converts sequences like "1/2", "3/4", and "1/8" into typographically correct fraction glyphs. Not all fonts support arbitrary fractions — some only include pre-composed fractions (1/4, 1/2, 3/4). Test with your specific font to see which fractions it supports.
Stylistic Sets (ss01-ss20)
Stylistic sets are collections of alternate character designs grouped by theme. A font can include up to 20 sets, each replacing certain characters with an alternate design. What each set does is entirely up to the font designer — there's no standard.
/* Activate stylistic set 01 */
.style-alt {
font-feature-settings: "ss01" 1;
}
/* Combine multiple sets */
.style-alt-multi {
font-feature-settings: "ss01" 1, "ss03" 1;
}
Examples in Popular Fonts
- Inter: ss01 = open digits (6, 9 with open counters), ss02 = disambiguated characters (distinguishes 1/l/I), ss03 = rounded corner brackets
- Source Sans Pro: ss01 = italic lowercase a (single-story)
- Recursive: Multiple stylistic sets for different character treatments in both casual and formal styles
- Fira Code: ss01-ss08 control specific ligature groups (arrows, equals, comparisons)
Stylistic sets are the most underused OpenType feature. They can dramatically change the personality of a font — from formal to friendly, from geometric to humanist — using the same font file you've already loaded.
Swashes and Alternates (swsh, salt, calt)
Decorative features for headings and display text:
swsh(Swash): Extended flourishes on capital letters. Common in serif and script fonts.font-feature-settings: "swsh" 1;salt(Stylistic Alternates): Individual character alternates that don't belong to a stylistic set.font-feature-settings: "salt" 1;calt(Contextual Alternates): Automatic alternate glyphs based on surrounding characters. On by default in most browsers. Used heavily in script fonts to connect letters naturally
/* Swash capitals for headings */
h1 {
font-feature-settings: "swsh" 1;
}
/* Stylistic alternates */
.decorative {
font-feature-settings: "salt" 1;
}
Case-Sensitive Forms (case)
The case feature adjusts punctuation and symbols to center vertically with capital letters. In regular text, parentheses, dashes, and colons are positioned for mixed-case text. In all-caps text, they can appear too low.
/* Activate case-sensitive forms for all-caps text */
.uppercase {
text-transform: uppercase;
font-feature-settings: "case" 1;
}Characters affected by case: parentheses (), brackets [], braces {}, dashes, colons, semicolons, and some mathematical operators. The feature shifts them vertically to sit centered between capital letters instead of baseline-aligned. This is a subtle but noticeable improvement for all-caps navigation items, buttons, and labels.
Discovering What Features a Font Supports
The hardest part of OpenType features is knowing what's available. Font files don't advertise their features; you need tools to inspect them.
Tools for Feature Discovery
- wakamaifondue.com: Drag a font file onto the page. It lists every OpenType feature with toggleable previews and generates ready-to-use CSS. This is the single best tool for OpenType feature discovery
- Chrome DevTools: In the Elements panel, click on a text element. The Computed tab shows
font-feature-settingsvalues. The Fonts section in Chrome's Rendering tab shows available axes for variable fonts - fonttools (Python):
from fontTools.ttLib import TTFont; font = TTFont("font.woff2"); print([t.tag for t in font["GSUB"].table.FeatureList.FeatureRecord])lists all GSUB (substitution) features - opentype.js (JavaScript): Load the font with
opentype.load()and inspect thegsubandgpostables programmatically - FontForge: Open the font, go to Element > Font Info > Lookups. Lists all features and their associated glyph substitutions
Quick Browser Test
Paste this CSS to quickly test if a font supports a specific feature:
/* Type sample text and toggle features */
.test {
font-family: 'Your Font';
font-size: 24px;
font-feature-settings: "liga" 1; /* change tag to test */
}
/* Test text for different features:
liga: fi fl ffi ffl
smcp: SMALL CAPS test
onum: 0123456789
tnum: 1111 vs 8888 (should be same width)
frac: 1/2 3/4 1/8
ss01-ss20: varies by font
*/If the text changes appearance when you add the feature tag, the font supports it. If nothing changes, it doesn't.
OpenType features are free upgrades for your typography. The font file you've already loaded likely contains ligatures, figure styles, small caps, and stylistic alternates that never render because you haven't activated them. Drag your WOFF2 into wakamaifondue.com, explore what's available, and add the relevant font-variant-* or font-feature-settings declarations to your CSS.
For maximum control, start with a professional font that includes extensive OpenType features. If your current fonts are in TTF or OTF format, convert to WOFF2 for web use — the conversion preserves all OpenType features. If you need to inspect a WOFF2 font's features, convert to TTF and open in FontForge.