
How to use the CSS font-feature-settings property?
- Sajjad
- Typography
- 11 Oct, 2026
The font-feature-settings property gives you direct, low-level control over OpenType features in a font. You write a comma-separated list of four-letter feature tags in quotes, each followed by an optional value, such as font-feature-settings: "tnum" 1, "ss01" 1, "liga" 0. A value of 1 or on turns a feature on, 0 or off turns it off, and higher numbers pick a specific alternate glyph. Because each declaration replaces the whole list rather than adding to it, it's easy to switch features off by accident, so the CSS Fonts specification recommends using the font-variant-* properties wherever they cover the feature you need. Use font-feature-settings for features that have no higher-level equivalent, such as stylistic sets in some setups, character variants or a font's custom features.
OpenType fonts often contain far more than their default glyphs: tabular figures, stylistic alternates, fractions, slashed zeros and much more. font-feature-settings is the one property that can reach every one of them, which makes it powerful and easy to misuse. In this article you'll learn the exact syntax, how to find which features a font supports, how inheritance trips people up and a pattern that avoids it, how it relates to the font-variant properties, and how to keep features working when you subset or self-host fonts.
The Syntax
The property takes normal or a list of feature tags:
.example {
font-feature-settings: "tnum" 1, "zero" 1;
}
The rules are strict:
- Tags are four characters: such as
"liga","kern","smcp"or"ss01". - Tags must be quoted: single or double quotes both work.
- Tags are case-sensitive:
"SMCP"is not the same as"smcp". Registered OpenType tags are lowercase. - Values are optional: if you omit the value, it defaults to
1, so"tnum"and"tnum" 1mean the same. - on and off are keywords:
"liga" offequals"liga" 0. - Numbers above 1 select alternates: for features with several alternates, like
"salt" 2or"cv03" 4, the number picks which one.
An invalid tag, such as one with five characters, makes the entire declaration invalid, so the browser ignores the whole line, not just the bad tag. If several features suddenly stop working, check for a typo.
Commonly Used Tags
| Tag | Feature | Typical use |
|---|---|---|
kern | Kerning | On by default; turn off only for testing |
liga | Standard ligatures | On by default; turn off for code or letter-spaced text |
dlig | Discretionary ligatures | Decorative headings |
tnum | Tabular figures | Tables, prices, timers |
pnum | Proportional figures | Running text |
onum | Old-style figures | Numbers within body text |
lnum | Lining figures | Numbers with capitals, UI |
zero | Slashed zero | Codes, serial numbers |
frac | Fractions | Recipes, measurements |
smcp | Small capitals | Abbreviations, labels |
c2sc | Capitals to small capitals | All-small-caps text |
case | Case-sensitive forms | Punctuation in all-caps text |
ss01 to ss20 | Stylistic sets | Alternate letter designs |
cv01 to cv99 | Character variants | Alternate forms of one character |
Which of these actually do anything depends entirely on the font. A feature the font doesn't contain is simply ignored.
Finding Which Features a Font Supports
Before writing CSS, find out what the font offers. There are three practical ways.
Check the Foundry's Documentation
Most good fonts have a specimen page listing features, often with examples. Google Fonts' pages and the specimen sites for open-source families such as Inter or IBM Plex typically document their stylistic sets and character variants.
Inspect the Font in a Browser Tool
Firefox's DevTools have a Fonts panel showing which font renders the selected text, and online tools such as Wakamai Fondue let you drop in a font file and see every feature with a live preview.
List Features with fontTools
For a definitive answer, read the font file directly with the Python fontTools library. Install it, along with Brotli for WOFF2 support:
pip install fonttools brotli
Then save this script as features.py:
import sys
from fontTools.ttLib import TTFont
font = TTFont(sys.argv[1])
tags = set()
for table in ("GSUB", "GPOS"):
if table in font:
for record in font[table].table.FeatureList.FeatureRecord:
tags.add(record.FeatureTag)
print(" ".join(sorted(tags)))
Run it against a font file:
python features.py fonts/Inter-Regular.woff2
aalt c2sc calt case ccmp cv01 cv02 cv03 cv04 cv05 dlig dnom frac kern liga locl mark mkmk numr onum pnum salt sinf smcp ss01 ss02 ss03 subs sups tnum zero
The output above is an abbreviated example; the exact list depends on the font and the version you have. GSUB features substitute glyphs, such as ligatures or alternates, and GPOS features adjust positioning, such as kerning.
The Inheritance Trap
font-feature-settings is inherited, which sounds convenient. The catch is that a new declaration on a child element replaces the inherited list entirely. It doesn't merge.
body {
font-feature-settings: "ss01" 1; /* a stylistic set for the brand look */
}
.price {
font-feature-settings: "tnum" 1; /* tabular figures for prices */
}
You might expect prices to have both the stylistic set and tabular figures. They don't. Inside .price, the computed value is only "tnum" 1, so ss01 is switched off and prices suddenly look different from the surrounding text.
The direct fix is to repeat every feature you want:
.price {
font-feature-settings: "ss01" 1, "tnum" 1;
}
That works for small sites but becomes fragile as more combinations appear.
A Pattern Using Custom Properties
A more maintainable approach uses one custom property per feature. Custom properties inherit and can be changed independently, and a single rule combines them:
:root {
--feat-ss01: "ss01" 0;
--feat-tnum: "tnum" 0;
--feat-zero: "zero" 0;
--feat-case: "case" 0;
}
*,
*::before,
*::after {
font-feature-settings:
var(--feat-ss01),
var(--feat-tnum),
var(--feat-zero),
var(--feat-case);
}
/* Turn features on wherever they're needed */
body { --feat-ss01: "ss01" 1; }
.price,
td.numeric { --feat-tnum: "tnum" 1; }
.order-code { --feat-zero: "zero" 1; }
.eyebrow { --feat-case: "case" 1; }
Now a price inside the body keeps ss01 from body and adds tnum from .price, because each feature is tracked separately. Adding a new feature means adding one variable and one line in the combining rule.
Prefer font-variant Where It Exists
The font-variant-* properties are the high-level way to switch on most common features. They don't have the inheritance trap, because each property only controls its own group of features, and they communicate intent more clearly.
| font-feature-settings | Higher-level equivalent |
|---|---|
"liga" 0 | font-variant-ligatures: no-common-ligatures |
"dlig" 1 | font-variant-ligatures: discretionary-ligatures |
"tnum" 1 | font-variant-numeric: tabular-nums |
"onum" 1 | font-variant-numeric: oldstyle-nums |
"frac" 1 | font-variant-numeric: diagonal-fractions |
"zero" 1 | font-variant-numeric: slashed-zero |
"smcp" 1 | font-variant-caps: small-caps |
"smcp" 1, "c2sc" 1 | font-variant-caps: all-small-caps |
"sups" 1 | font-variant-position: super |
"kern" 0 | font-kerning: none |
So the price example is better written as:
body {
font-feature-settings: "ss01" 1;
}
.price {
font-variant-numeric: tabular-nums;
}
Now .price inherits ss01 and adds tabular figures, with no repetition. You can also combine several numeric values in one declaration, such as font-variant-numeric: tabular-nums slashed-zero.
A useful bonus of font-variant-caps: small-caps is that if the font has no small caps, browsers can synthesise them. With "smcp" 1, nothing happens if the feature is missing.
When font-feature-settings Is Still the Right Tool
Some features have no dedicated font-variant value, or only have one through a more involved mechanism:
- Character variants (
cv01and up): such as Inter's alternate forms of individual letters. - Stylistic sets (
ss01toss20): these can be reached throughfont-variant-alternateswith an@font-feature-valuesrule, but that requires naming each set per font family.font-feature-settingsis often simpler. - Contextual alternates (
calt): on by default; turning them off needsfont-variant-ligatures: no-contextualor"calt" 0. - Case-sensitive forms (
case): nofont-variantvalue exists for this. - Custom or uncommon features: some fonts include features specific to the foundry.
Using @font-feature-values for Alternates
For completeness, here's the higher-level way to use a stylistic set. It's supported in current versions of Chrome, Edge, Firefox and Safari:
@font-feature-values Inter {
@styleset {
open-digits: 1;
round-quotes: 3;
}
}
.brand-heading {
font-family: Inter, sans-serif;
font-variant-alternates: styleset(open-digits, round-quotes);
}
This maps readable names to ss01 and ss03 for Inter only. The benefit is that if the font changes, the names no longer apply, instead of accidentally triggering an unrelated stylistic set in the new font.
Order of Precedence
When both mechanisms touch the same feature, the CSS Fonts specification defines the order. Features enabled by default (such as kern and liga) are applied first, then font-variant-* and font-kerning settings, then font-feature-settings last. In other words, the low-level property wins:
.demo {
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 0; /* this wins: figures are proportional */
}
That's useful for overriding a component library's defaults, but it also means a stray font-feature-settings can silently cancel a font-variant rule.
Variable Fonts and Features
font-feature-settings controls OpenType layout features, not variation axes. Weight, width, slant and custom axes in variable fonts use font-variation-settings or, better, high-level properties like font-weight and font-stretch. The two are often confused because they have similar syntax:
.headline {
font-weight: 650; /* variation axis via high-level property */
font-variation-settings: "opsz" 48; /* low-level axis setting */
font-feature-settings: "ss02" 1; /* layout feature */
}
Both low-level properties share the same inheritance trap, so the custom property pattern above applies to font-variation-settings too.
Keeping Features When You Subset or Self-Host
Features live in the font file, so if the file doesn't contain them, no CSS will help. Two common ways to lose them:
- Hosted font services: some services subset fonts for size and may not include every OpenType feature in every file they serve. If a feature works locally but not on the live site, compare the files.
- Your own subsetting: tools like
pyftsubsetkeep only a default list of layout features unless told otherwise.
To keep all features while subsetting to Latin characters:
pyftsubset Inter-Regular.ttf \
--unicodes="U+0000-00FF,U+2000-206F,U+20AC" \
--layout-features='*' \
--flavor=woff2 \
--output-file=Inter-Regular-latin.woff2
To keep only specific features and save a little more space, list them instead, for example --layout-features='kern,liga,calt,tnum,ss01'.
If you're applying features to data-heavy interfaces, tabular vs proportional numerals explains which figure style suits which content.
FAQ: font-feature-settings
It turns individual OpenType features in a font on or off, such as ligatures, tabular figures, small caps, stylistic sets and character variants. It works only if the font actually contains the feature.
Each font-feature-settings declaration replaces the full list instead of adding to it. A child rule with one tag switches off features set on its parent. Repeat all the tags you need, use font-variant properties, or use one custom property per feature.
Use font-variant properties when they cover the feature, because they're clearer and don't override unrelated features. Use font-feature-settings for features without a font-variant equivalent, such as character variants or case-sensitive forms.
Check the foundry's specimen page, load the file into a feature inspection tool such as Wakamai Fondue, or list the GSUB and GPOS features with the Python fontTools library.
Yes. It's supported unprefixed in all current versions of Chrome, Edge, Firefox and Safari. Very old browsers needed vendor prefixes, but those are no longer necessary.
It works with any font that includes the feature. Hosted files may not always include every feature the original font has, so if a feature doesn't appear, try self-hosting the full font or check the file with fontTools.
Conclusion
font-feature-settings is the most direct way to control OpenType features: a list of quoted four-letter tags with optional values that switch features on, off or to a specific alternate. It reaches everything a font offers, from stylistic sets and character variants to case-sensitive punctuation, and it wins over all other feature settings when they conflict.
That power comes with one major catch: each declaration replaces the inherited list. Reach for font-variant-* properties first, use font-feature-settings for what they can't do, and adopt the custom property pattern if you need several features in combination. Check which features your font really contains, keep them when you subset, and your typography can make full use of the details type designers built in.


