A Manual For font-variant-alternates

a comparison between two version of the word ailuridae written with and without font variant changes. The font variant alternative css rule is generally well supported by evergreen browsers (read: all of the ones that matter) and is incredibly useful if you have a well-constructed font to work with. I’m using the ever-wonderful font Concourse for this site, and I wanted the logo on the left to match the version I had drafted in Affinity Designer with some alternative characters. This should’ve been easy. The lower case L should be the hooked variant, and the lowercase U should use the more rounded version.

However, all of the information you’re going to find while looking this up will be reiterations of the content found on MDN which I find really confusing. There’s a whole bunch of detail left out about how the property works, and why it’s required to also use the @font-feature-values rule. After some digging, this is the details I was hoping to find a couple hours ago.

OpenType Features

The OpenType font format allows storing alternate glyphs/settings using a few different named categories called “features”. Each of these categories has some restrictions on what is changeable, but they are all indexed and configured the same way: toggling on or off using a unique ID that is a short string representing the name of the feature, and sometimes a sequential unique number if there’s multiple styles. Here’s a short sample of a few of these toggles you can pull.

Feature ID
Small capitals smcp
Replace large size capitals with small caps c2sc
Unicase (use only lowercase glyphs) unic
Use Simplified Chinese characters smpl
User superscript numbers (Ordinals) ordn
Use glyph alternatives with more flourishs (Swash) swsh
Use a group of style settings (Stylistic Set) ss01 - ss99

A large list of these can be found on wikipedia, of course. It’s important to note that these have no other configuration options than on or off. It’s up to the font designer to make sure that they sort the glyphs and settings into the correct categories, and that they are configurable enough that someone won’t be stuck with a style feature that does more than they want. Also note that there is not alternative Swash set. That feature only has one feature ID, so therefore you can only have 1 entire new set of swish-y characters per font. (At least without using a stylistic set.)

If we want to use alternative glyphs, we first need to determine what features are implemented in a given font. This is sometimes provided from your font designer, or you can check manually using a tool like fontdrop.info which will give you an interactive list of toggles to see what each feature does. If you see a number of features named something like ss18, (Style Set 18) these are most likely your single alternate characters. This is what I was looking for when I was trying to find out how to change the L and U glyphs for my logo. Just keep messing with these until you know which stylistic sets you’d like to be enabled to get the font preview looking how you want it to.

If you don’t see any style set features, jump to the bottom of this page for more info.

The @font-feature-values rule

Because these style sets are named using cryptic 4-character IDs, CSS graciously specs a rule that lets us group together the various feature IDs you want to use and assign each feature a name. Here’s what the @font-feature-values rule looks like for this website, as used by the logo on the left.

@font-feature-values concourse {
    @styleset {
        altu: 15;
        altl: 17;

This is where I’ve been sinking time into reading W3C spec documents and intellisense in my editor. I don’t see this weird syntax being talked about much. So lets break down what each part does.

@font-feature-values concourse

We declare this is a font feature set for the concourse font. This must be the full name of the font family you wish to configure. You cannot share font feature settings between font families, but if you have multiple styles (bold, italic) of the same font family that use the same font family name, these settings will apply to those as well.


This block signals that everything inside it is referring to a Stylistic Set feature. There are other @rules for other feature categories. You may need to use the @character-variant rule if you found no style set rules. See the bottom of this page for more info.

It’s also important to note, these @rules are not plural! (@styleset not @stylesets) They are always singular, despite the fact that the renamed OpenType features it defines must be individually applied when using the font-variant-alternates rule that we will get to. This is not one style set, but a renamed list of style sets.

altu: 15 and altl: 17

This was just a name that I came up with. Confusingly for the CSS spec, the properties here aren’t known CSS properties. You write in your own names here. You can write anything you want as a rule name here. The number we assign our feature to, is the Stylistic Set we want to use when we refer to that name. This is so supremely confusing to me. It really feels more complicated than it has to be. 15 here, means I want to name OpenType feature ss15 as altu, because it’s the stylistic set that contains the alternate glyph for the rounded U character that I want when enabled.

Applying the renamed OpenType features to an element

Now we have some font features, assigned to different names, and a font in which to use those. Currently, nothing has changed when rendering this font. For us to apply the configurations we want we need to use the font-variant-alternates rule which allows use to toggle on and off the features we named in a @font-feature-values rule. This actually introduces a really odd syntax choice where we need to re-assert what category of features we want to apply.

h1 {
    font-family: concourse, sans-serif;
    font-variant-alternates: styleset(altu, altl);

First we set the font family to the one that we wrote the @font-feature-values rule for, then we assign the variant features using the feature category’s css function. In my case, all of the alternates that I wanted were assigned to style sets. The values you pass in are the property names you gave each rule, and they can be comma separated if you want to list multiples. If they are listed, it’s the same as turing that feature ON.

You should now see your selected alternate characters being rendered. Yay!


I don’t have any style sets

If you don’t have any style sets in your font (and you’re 100% sure that the font file isn’t malformed or missing alternate characters all-together), the alternate characters may be stored as character variants. Which each get their own feature. For whatever reason, I’ve found this to be less common, but I imagine there are fonts out there like this. (Probably quite a few, I’ve only dug into a couple fonts really.) Character Variants use the feature ID cv01 to cv99, so in that way they are similar to style sets… except I lied at the start of this page. You can actually pass more information to character variants beyond just ON and OFF. You are given one whole integer to send over. This integer you send over, is the choice of which variant to use. There may be more than 2 different kinds of “a” glyph for example. This means that the CSS is slightly different for character variant rules.

@font-feature-values concourse {
    @character-variant {
        alta1: 4;

h1 {
    font-family: concourse, sans-serif;
    font-variant-alternates: character-variant(alta1);

The most confusing part of this is the @font-feature-values rule. The name you give the feature must end in a number. That number refers to the integer passed to the character variant feature. So in this case, I am asking for the 1st alternative. I called it alta1, so we could imagine this is an alternative single-storey “a” glyph.

The 4 in the value side of this rule, refers to the cvXX number in the ID, as it does with style sets. This is actually what tells the font we are looking for glyphs replacing a specific letter. You need to refer to your font data to find what this value is. They will show up as cvXX features. Just enter the number with out the cv part.

I don’t have any alternative characters at all!

Some fonts don’t have any! Though it’s also good to try using a different format first. Sometimes the WOFF2 or WOFF font may be lacking features that are in the OTF file. If you have access to a OTF file, try doing the same font inspection I mentioned under the first heading and see if you see any alternates. If so, you can convert an OTF font to WOFF2, or even use it in the browser as-is. Though I would suggest seeking to only serve webfonts in WOFF2 format.