Vercel's Geist typeface ships with a surprisingly deep set of OpenType controls. Most teams install it, set a font stack, and stop there — leaving tabular number alignment, alternative letterforms, and a handful of genuinely useful glyph variants sitting unused. This guide covers every documented feature, with copy-pasteable CSS and Tailwind v4 patterns for each one.
One hard prerequisite before anything else: the Google Fonts build of Geist does not ship the complete OpenType table. If you're loading Geist from fonts.googleapis.com, stylistic sets will not work. To unlock the full feature set, install from npm (npm i geist) or download the ZIP from vercel.com/font and self-host the .woff2 files.
How font-feature-settings works
The CSS property font-feature-settings is the low-level toggle for OpenType features. Each feature has a four-character tag and an integer value — 1 to enable, 0 to explicitly disable. Multiple features are comma-separated:
/* enable tabular numbers and slashed zero */
body {
font-feature-settings: "tnum" 1, "zero" 1;
}
The higher-level font-variant-* properties exist for some features (e.g., font-variant-numeric: tabular-nums), but font-feature-settings is more portable and covers everything Geist exposes.
Geist Sans: complete feature reference
Standard ligatures — liga
Geist Sans includes standard ligatures for the classic combinations: ff, fi, fl, and tt. These are on by default in most renderers. You rarely need to touch this setting for body text, but you may want to disable it in contexts where character-by-character fidelity matters (form inputs, code-adjacent text):
/* disable standard ligatures */
.no-lig {
font-feature-settings: "liga" 0;
}
Discretionary ligatures — dlig
The dlig feature adds ligatures that are stylistic rather than essential — in Geist's case, AI and UI pairs. These are off by default and only worth enabling in logotype-sized display text:
.display-heading {
font-feature-settings: "dlig" 1;
}
Tabular numbers — tnum
By default Geist Sans uses proportional numbers, which look natural in running text but cause columns to jitter when values change (dashboards, tables, pricing). tnum fixes each digit to the same advance width:
/* consistent digit width for tables and numeric displays */
.stat, td, .price {
font-feature-settings: "tnum" 1;
}
Fractions — frac
Enable diagonal fraction formatting automatically from slash-separated numerals:
.recipe, .measurement {
font-feature-settings: "frac" 1;
}
/* "1/2" renders as a properly formatted ½ glyph */
Ordinals — ordn
Raises the suffix letters of ordinal indicators (1st, 2nd, No.) to superscript automatically:
.ordinal {
font-feature-settings: "ordn" 1;
}
Geist Sans: stylistic sets
Stylistic sets are opt-in alternate glyph designs. Geist Sans ships eight of them. None are on by default — you choose which aesthetic you want.
ss01 — No-tail a
Replaces the double-storey a with a single-storey (no-tail) form. Cleaner at small sizes; popular for UI labels and navigation:
.nav, .label {
font-feature-settings: "ss01" 1;
}
ss02 — Alt a
A second alternative a design with a different treatment from ss01. Useful if you want a warmer, slightly more humanist feel than the default geometric form.
ss03 — Alt l
Provides a serifed or tailed variant of the lowercase l, reducing ambiguity with 1 and I in low-context settings like alphanumeric codes:
.serial-number, .license-key {
font-feature-settings: "ss03" 1;
}
ss04 — Alt R
An alternate uppercase R with a different leg treatment — subtle, but noticeable in large headings.
ss05 — Alt I
Adds serifs to the capital I, which otherwise looks identical to a lowercase l in a geometric sans-serif. Almost always worth enabling for any UI that displays user-entered strings, IDs, or passwords:
.id-display, .monospace-fallback {
font-feature-settings: "ss05" 1;
}
ss06 — Alt G
An alternate G form. The default has a spur; ss06 offers a spurred or spur-less alternative depending on the exact build variant.
ss08 — Rounded dots
Modifies diacritical marks and dot punctuation to be rounder. Softens the overall texture of text, particularly noticeable in European languages with heavy diacritic use.
ss09 — Alt numbers
Replaces the default numeral set with alternate designs. Worth testing if the default figures look too mechanical for your brand.
ss10 — Alt enclosing shapes
Adjusts the design of brackets, parentheses, and angle brackets. Useful in technical documentation where these characters appear frequently.
Geist Mono: what's different
Geist Mono shares the same numeric and structural features as Geist Sans, but its relationship with ligatures has its own history worth understanding.
The ss11 situation: coding ligatures
Geist Mono v1.7.0 accidentally activated programming ligatures (-->, ==, !=, ..., --, and others) under the standard liga feature — which is on by default everywhere. This broke monospace alignment in editors when innocuous strings like --debug-flag were rendered as ligated glyphs. The v1.7.1 patch moved all coding ligatures to Stylistic Set 11 (ss11), which is off by default.
The practical consequence: in Geist Mono, liga is now safe to leave on (it only covers the basic non-coding ligatures). If you want arrow and equality ligatures in a code block, opt in explicitly:
/* opt into coding ligatures in Geist Mono */
pre, code {
font-family: "Geist Mono", monospace;
font-feature-settings: "ss11" 1;
}
If you are displaying code where exact character identity matters — diffs, terminals, symbol-heavy output — disable ligatures entirely and enable the number clarity features:
/* code block: no ligatures, clear numbers */
pre, code {
font-family: "Geist Mono", monospace;
font-feature-settings:
"liga" 0,
"calt" 0,
"tnum" 1,
"zero" 1;
}
The "zero" 1 feature enables a slashed zero, which removes ambiguity between 0 and O — essential in any code or key display context.
Tailwind v4 integration
Tailwind CSS v4 uses a CSS-first configuration model. You define fonts and their defaults in a @theme block, and arbitrary feature values are applied inline with the font-features-[...] utility.
Loading Geist and setting defaults in @theme
/* app.css */
@font-face {
font-family: "Geist Sans";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/GeistVariableVF.woff2") format("woff2");
}
@font-face {
font-family: "Geist Mono";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/GeistMonoVariableVF.woff2") format("woff2");
}
@import "tailwindcss";
@theme {
--font-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
--font-mono: "Geist Mono", ui-monospace, monospace;
/* default feature settings baked into the font-sans utility */
--font-sans--font-feature-settings: "ss01" 1, "ss05" 1, "tnum" 0;
/* for code: no ligatures, slashed zero, tabular digits */
--font-mono--font-feature-settings: "liga" 0, "calt" 0, "zero" 1, "tnum" 1;
}
With this setup, every element that carries font-sans or font-mono automatically inherits the correct OpenType defaults. No per-component declarations needed.
Per-element overrides with arbitrary values
Tailwind v4's arbitrary value syntax lets you apply features inline without writing custom CSS:
<!-- enable tabular numbers on a stats widget -->
<span class="font-features-['tnum']">1,284</span>
<!-- combine multiple features -->
<span class="font-features-['tnum','zero','ss03']">0l1I</span>
<!-- reference a CSS variable -->
<span class="font-features-(--code-features)">...</span>
The parenthesis syntax (font-features-(--var)) is useful when you want to swap feature sets via JavaScript or a data attribute without touching class names.
Responsive feature switching
Breakpoint prefixes compose with font-features utilities just like any other Tailwind utility:
<p class="font-features-['onum'] md:font-features-['tnum']">
2,500 active users
</p>
Practical combinations worth knowing
Rather than enabling features one at a time, these three combinations cover most real use cases:
/* UI labels, nav, small caps context: legible alternates */
.ui-label {
font-feature-settings: "ss01" 1, "ss05" 1;
}
/* Pricing, tables, dashboards: aligned numbers */
.numeric {
font-feature-settings: "tnum" 1, "zero" 1;
}
/* Inline code in prose: disable all ligatures, slashed zero */
code {
font-family: "Geist Mono", monospace;
font-feature-settings: "liga" 0, "calt" 0, "zero" 1, "tnum" 1;
}
If you want coding ligatures in display code examples (not editable areas), add "ss11" 1 to the code rule. Keep it off in anything where the user might need to read individual characters precisely.
One common mistake to avoid
Setting font-feature-settings on a parent does not merge with declarations on children — it replaces them. If you set "tnum" 1 on body and then "ss01" 1 on h2, the heading loses tabular numbers. Always list every feature you need in a single declaration per selector, or use CSS custom properties to compose them:
:root {
--base-features: "tnum" 1, "zero" 1;
}
h2 {
/* inherits nothing from body's font-feature-settings */
font-feature-settings: var(--base-features), "ss01" 1, "ss05" 1;
}
Geist is a well-considered typeface and the OpenType table reflects that. A handful of targeted feature declarations — tabular numbers for data, the serifed I for codes and IDs, and correct ligature control in Geist Mono — will noticeably improve the polish of any interface that uses it. None of these changes require a new font or a redesign; they are already in the file, waiting for the right CSS.