font-variant-alternates: OpenType Features for Typographic Refinements
AI generated
{ }
@
CSS · Typography · OpenType
font-variant-alternates
Using OpenType features for typographic refinements deliberately

Every professional font file holds far more than a single character set: ligatures, alternate glyphs, small caps and stylistic sets already sit ready inside the font file, but stay unused until something addresses them through CSS. font-variant-alternates and its sibling properties make exactly that possible, without JavaScript and without loading extra font files.

13 min read OpenType · GSUB/GPOS font-variant-* · font-feature-settings

1. OpenType features on the web: why dedicated font-variant properties exist

OpenType font files store, in addition to plain glyph outlines, so-called features, named rules inside the file's GSUB and GPOS tables that define how certain character sequences should be substituted or positioned. A feature tagged liga, for instance, replaces the letter sequence fi with a joined ligature glyph, a feature like smcp replaces lowercase letters with small-caps glyphs. Without CSS addressing them, all of these features stay unused, even when the font file ships them.

CSS offers two layers for this: the font-variant-* properties (font-variant-ligatures, font-variant-alternates, font-variant-caps, font-variant-numeric, font-variant-east-asian) address typographic concepts through readable values, without requiring authors to know the underlying four-letter OpenType tags. font-feature-settings, by contrast, works at a lower level directly with those tags. For most use cases the font-variant properties are the better choice, because they read more clearly and the browser falls back cleanly to the default value whenever support is missing.

2. font-variant-ligatures: standard, discretionary and historical ligatures

font-variant-ligatures controls which ligatures the browser activates. The value common-ligatures (the default in most browsers) turns on standard ligatures like fi or fl, which are present in nearly every professional typeface and usually improve legibility without drawing attention. discretionary-ligatures turns on additional, decorative ligatures a typeface optionally ships, for example ornate connections between specific letter pairs meant to stand out deliberately.

historical-ligatures activates ligatures rooted in historical writing conventions, typically used today in editorial or decorative contexts, for example invitation cards or stylized headlines. no-common-ligatures explicitly disables even the standard ligatures, which can make sense in technical contexts where every character sequence must remain individually recognizable, for example in code blocks or when rendering variable names.

3. font-variant-alternates: stylistic sets, character-variant and swash

font-variant-alternates reaches more complex OpenType features that a typeface often ships in several named variants, for example styleset() for stylistic sets (ssXX tags), character-variant() for individual alternate glyph shapes (cvXX tags), and swash() for ornate leading letters. Because these features are named and numbered differently per typeface, font-variant-alternates additionally requires an @font-feature-values block that maps a readable name to the typeface-specific number.

That indirection may look cumbersome at first glance, but it keeps the same CSS code readable even when different fonts with different internal numbering are used behind the scenes. Without a matching @font-feature-values block, the browser silently ignores the styleset() or character-variant() instruction and falls back to the default glyphs, which makes debugging harder if the block is missing or contains a typo.


@font-feature-values "Custom Serif" {
  @styleset {
    swashy-caps: 1;
  }
  @character-variant {
    alt-ampersand: 2;
  }
}

h1 {
  font-family: "Custom Serif", serif;
  font-variant-alternates: styleset(swashy-caps) character-variant(alt-ampersand);
}

4. font-variant-caps: small caps, unicase and titling caps

font-variant-caps bundles several uppercase variants under one property. small-caps replaces lowercase letters with smaller, genuine small-caps glyphs (not simply scaled-down uppercase letters), which produces a noticeably calmer look than text-transform: uppercase, particularly for abbreviations inside body text. all-small-caps applies the same substitution to already uppercase letters as well, producing a fully uniform small-caps appearance.

titling-caps delivers uppercase glyphs specifically optimized for large sizes, which often look more balanced in headlines than the typeface's regular capitals. unicase turns on a unified glyph shape where uppercase and lowercase become visually nearly indistinguishable, a stylistic device used mostly in reduced, geometric typography. All these values only take effect if the typeface in use actually ships the matching OpenType features, otherwise the rendering stays unchanged.


.abbreviation {
  font-variant-caps: small-caps;
}

h1.editorial {
  font-variant-caps: titling-caps;
}

5. font-feature-settings as a low-level fallback: when it is still needed

font-feature-settings addresses OpenType features directly through their four-letter tags, for example font-feature-settings: 'liga' 1, 'kern' 1. That is more powerful because it can reach any feature a typeface ships, including ones without a dedicated font-variant property, for example stylistic-set combinations outside the standard range or experimental features from individual foundries.

The downside: font-feature-settings bypasses the semantic layer entirely, a value like 'ss01' 1 says nothing about what that feature actually does, and multiple font-feature-settings declarations at different cascade levels overwrite each other completely instead of merging, unlike the font-variant properties. In practice the rule of thumb is: try font-variant-* first, and reach for font-feature-settings only for features that have no dedicated property.

6. A practical recipe: a robust @font-feature-values fallback chain

Because styleset() and character-variant() numbers vary between typefaces, it pays to write one @font-feature-values block per typeface in use, consistently mapping the same readable names to the correct internal number for each. That keeps the actual CSS code using font-variant-alternates independent of which specific typeface is currently loaded, as long as a matching block exists for each font.

For typefaces without the desired feature, font-variant-alternates simply stays inert, the browser automatically falls back to the default glyphs without producing an error. That property makes font-variant-alternates progressive enhancement in the real sense: whoever loads the matching typeface sees the refinements, whoever uses a different typeface or falls back to a fallback font still sees readable, correct text.


@font-feature-values "Editorial Display" {
  @styleset {
    swashy-caps: 1;
  }
}

@font-feature-values "Fallback Serif" {
  @styleset {
    swashy-caps: 3;
  }
}

.headline {
  font-family: "Editorial Display", "Fallback Serif", serif;
  font-variant-alternates: styleset(swashy-caps);
}

7. Interaction with variable fonts: font-variation-settings is a different thing

A common misunderstanding is treating font-variant-alternates and font-variation-settings as the same concept, even though they address entirely different technologies. font-variant-alternates turns on OpenType features (GSUB/GPOS) that swap in fixed, alternate glyphs. font-variation-settings, by contrast, controls a variable font's continuous design axes, for example weight (wght) or width (wdth), which interpolate smoothly between defined values instead of replacing one fixed glyph with another.

Both mechanisms can be combined in the same typeface and the same CSS rule block: a variable font can ship both variable axes for weight and width as well as classic OpenType features for ligatures and small caps. In practice that means using font-variation-settings for axis control and applying font-variant-alternates, respectively the other font-variant properties, in parallel to activate fixed glyph substitutions, without mixing the two concepts up.


.headline {
  font-family: "Variable Display";
  font-variation-settings: "wght" 650, "wdth" 110;
  font-variant-ligatures: discretionary-ligatures;
  font-variant-caps: small-caps;
}

8. Performance and browser support: a test strategy for production use

The font-variant properties themselves cost no noticeable rendering performance, because glyph substitution happens during text shaping, a step the browser runs for every piece of text rendering anyway. The real cost question concerns the font file: fonts with many OpenType features tend to be larger, because extra glyphs and feature tables need to ship along, which should be weighed when picking a typeface for performance-critical pages.

For checking support, a simple visual comparison across the relevant target browsers is usually enough in practice, because font-variant-alternates and font-variant-caps are fault tolerant: when support or the matching feature in the typeface is missing, the default glyph simply gets shown, never a broken or faulty layout. That allows using these features in production without building elaborate browser branches or @supports checks.

9. Practical example: an editorial layout with a swash headline and plain body text

A typical editorial layout applies OpenType features deliberately and sparingly: the large headline gets swash() for an ornate leading letter and discretionary-ligatures for decorative character, while the body text deliberately keeps only common-ligatures, so legibility across large amounts of text is never put at risk. Too many activated alternates inside body text quickly look busy and distract from the actual content.

Abbreviations inside body text, such as company names or technical acronyms, on the other hand deliberately benefit from font-variant-caps: small-caps, because they blend into the flow of text visually better than fully uppercase words, which inside body text often read like an exclamation. That deliberate, context-dependent combination of different features is the real advantage over a blanket text-transform solution.

Property Purpose Needs @font-feature-values Typical use
font-variant-ligatures Standard, discretionary, historical ligatures No Body text, headlines
font-variant-alternates styleset(), character-variant(), swash() Yes Editorial layouts, logos
font-variant-caps small-caps, titling-caps, unicase No Abbreviations, large headlines
font-feature-settings Direct access to any OpenType tag No Features with no dedicated property
font-variation-settings Continuous variable font axes No Weight, width, optical size

Mironsoft

Modern CSS, layout architecture and rendering performance

CSS that stays maintainable instead of breaking with every change?

We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.

CSS Audit

Systematically uncovering specificity issues, cascade conflicts and unused selectors.

Architecture Refactoring

Introducing cascade layers, custom properties and design tokens cleanly.

Performance Tuning

Fixing layout thrashing, expensive selectors and rendering bottlenecks.

10. Summary

font-variant-alternates: The Essentials at a Glance

Core idea

OpenType font files hold features in GSUB/GPOS tables that only get activated through CSS properties like font-variant-alternates.

Named values

styleset() and character-variant() need an @font-feature-values block that maps readable names to the typeface-specific feature number.

Fallback safety

If a feature is missing from a typeface, the browser silently falls back to the default glyph, a broken layout never results.

Variable fonts

font-variation-settings controls continuous axes like weight, font-variant-alternates turns on fixed glyph substitutions, and both can be combined.

11. FAQ: font-variant-alternates: The Essentials at a Glance

1What is the difference between font-variant-alternates and font-feature-settings?
font-variant-alternates addresses typographic concepts through readable names and needs an @font-feature-values block for named values. font-feature-settings works directly with four-letter OpenType tags and is correspondingly less readable.
2Why do I need an @font-feature-values block?
It maps a readable name to the typeface-specific number of a stylistic set or character variant, since those numbers differ between typefaces.
3What happens if a typeface does not support a feature?
The browser ignores the instruction and shows the default glyph, no error and no broken layout results. That makes the font-variant properties safe to use in a fault-tolerant way.
4Is font-variant-caps: small-caps the same as text-transform: uppercase?
No. small-caps applies genuine, smaller small-caps glyphs from the font file, text-transform: uppercase merely scales existing uppercase letters and changes no glyph shape.
5Can I combine font-variant-alternates with variable fonts?
Yes, both mechanisms are independent. font-variation-settings controls continuous axes like weight, font-variant-alternates activates fixed OpenType features in parallel.
6Should I generally enable ligatures in body text?
common-ligatures is already the default in most browsers and improves legibility unobtrusively. discretionary- and historical-ligatures should stay reserved for headlines or decorative contexts instead.
7How do I find out which features a typeface ships?
Font inspection tools like Wakamai Fondue or the respective foundry's developer documentation list the available OpenType feature tags and the numbering of the stylistic sets.
8Does font-variant-alternates cost noticeable rendering performance?
No, glyph substitution happens during the text shaping step the browser already performs. The relevant cost question is more about file size for typefaces with many extra feature glyphs.
9Do I need @supports for font-variant properties?
Not in practice, since the properties themselves are fault tolerant and get simply ignored without support. @supports is only worth it for very specific, rare values.
10What does swash() actually do?
swash() activates the OpenType feature for ornate, decorative leading or trailing letters that a typeface ships as its own glyph variant, usually intended for headlines or invitation cards.