OpenType and Advanced CSS Typography
The difference between type that is merely correct and type that is genuinely refined lies mostly in details invisible at a glance — and accessible with a single line of CSS.
- What OpenType features are and why they matter
- Ligatures, kerning, stylistic sets, small caps
font-feature-settingsandfont-variantin practice- Variable fonts: what they offer for print output
There is a moment in typographic education when you look at a piece of professionally typeset text and something feels different about it — more settled, more complete — without your being able to say exactly why. The words are the same words. The typeface is one you recognise. But the text has a quality that your own typesetting lacks, a kind of finish. This chapter is largely about that quality. It lives in the details: the ligatures that bind certain letter pairs into elegant single forms, the old-style numerals that sit in the text without towering above the lowercase, the small capitals that mark proper nouns and abbreviations without the brashness of full capitals. These are OpenType features, and they are available to you in CSS with almost no effort. Most designers never use them. You will.
OpenType is a font format, developed jointly by Adobe and Microsoft in the 1990s, that extended the capabilities of earlier formats by allowing a single font file to contain multiple alternate letterforms, contextual substitutions, and typographic refinements. Where older formats stored only one glyph per character, an OpenType font can store many — a standard form and a swash form, lining and old-style numerals, ligatures, small capitals, and dozens of other variants — and make them accessible through a standardised system of feature tags. CSS exposes this system through two properties: font-feature-settings and the higher-level font-variant shorthand.
Ligatures When two letters become one
A ligature is a single glyph that replaces two or more adjacent characters whose shapes, when set separately, create an awkward collision or gap. The most common ligatures in Latin typography are fi and fl, where the dot of the i or the ascender of the l collides with the hook of the f. Without a ligature, this collision looks like a typographic accident. With one, the characters are joined into a single harmonious form that resolves the tension.
Ligatures come in two categories. Standard ligatures — principally fi, fl, ff, ffi, and ffl — are those where the typographic problem being solved is genuine: the characters genuinely collide or create an awkward space without the ligature, and their use improves the text's appearance in an objective and broadly agreed-upon way. These should be on by default for all body text. Discretionary ligatures are more elaborate forms — ct, st, Th, and various contextual swash connections — that are primarily decorative rather than corrective. They should be used with care and restraint, typically only at display sizes or in contexts where a period-appropriate elegance is the goal.
In CSS, standard ligatures are enabled with font-feature-settings: "liga" 1, "clig" 1, or more cleanly with font-variant-ligatures: common-ligatures contextual. Modern browsers enable common ligatures by default for most typefaces, but it is good practice to specify them explicitly to ensure consistent behavior across rendering environments — particularly important for a Paged.js document where the same HTML may be rendered in different browsers.
Numerals Old-style vs. lining, proportional vs. tabular
Numerals come in two height variants and two spacing variants, and understanding when to use each is one of the most immediately visible improvements you can make to professionally typeset text.
Lining numerals — also called titling or ranging numerals — are all the same height, roughly equal to the cap height. They are the default in most digital fonts, and they are appropriate when numerals appear in isolation, in headlines, or in tables where visual alignment with capital letters matters. In body text, however, lining numerals create a problem: they are taller than the surrounding lowercase letters, which makes each numeral lurch upward from the text like a capital letter in an otherwise lowercase word. The number 1492 set in lining numerals in a line of body text reads as four capitals in a row.
Old-style numerals — also called text numerals or lowercase numerals — have varying heights, with some extending to the cap height (1, 2, 7), some sitting on the baseline (0, 1, 2, 3, 4), and some descending below it (3, 4, 5, 6, 7, 8, 9, depending on the typeface). They are designed to blend into running text the way lowercase letters do, without creating visual interruption. For any document where numerals appear within body text — dates, quantities, citations — old-style numerals are the correct choice and produce significantly more refined results.
Proportional numerals have varying widths, like letters, and read naturally in text. Tabular numerals are all the same width, so columns of numbers align vertically regardless of which digits appear. Tabular numerals are essential for tables and any columnar arrangement; proportional numerals are better for text. Most OpenType fonts provide all four combinations: proportional lining (default in most fonts), proportional old-style, tabular lining, and tabular old-style.
In CSS: font-variant-numeric: oldstyle-nums for old-style proportional, font-variant-numeric: lining-nums tabular-nums for tabular lining in tables. The shorthand is cleaner and more legible than the equivalent font-feature-settings values ("onum" 1, "tnum" 1, "lnum" 1) and should be preferred.
Figure 10.1 — Lining versus old-style numerals in body text. Left: lining numerals in a passage of body text — the numerals 1893, 47, and 3rd are as tall as capital letters, creating visual disruption in the flow of lowercase text. Right: old-style numerals in the same passage — the numerals have varying heights that integrate naturally with the surrounding lowercase letters. The text reads more smoothly, and the numerals no longer call attention to themselves.
Small capitals The correct tool for abbreviations and proper nouns
Small capitals are capital letterforms drawn at approximately the x-height — the height of the lowercase letters — rather than at the full cap height. They exist to solve a problem that occurs frequently in professional typesetting: the need to set an abbreviation or proper noun in capitals without the visual disruption of full-height capitals in a line of lowercase text.
Consider the difference between a line reading "The BBC broadcast the announcement" and one reading "The BBC broadcast the announcement." In the first, the abbreviation is set in true small capitals — drawn to match the weight and texture of the lowercase letters around them. In the second, using regular capitals, BBC towers over the surrounding text, pulling the eye and interrupting the reading flow.
True small capitals are distinct from mechanically scaled capitals — a common fake that produces spindly, light forms because it simply reduces the full capitals rather than drawing new ones. Every properly made OpenType font includes true small capital glyphs. In CSS, they are accessed with font-variant-caps: small-caps, which applies to lowercase letters converted to small caps, or font-variant-caps: all-small-caps, which converts all letters including already-capitalised ones. The latter is what you want for abbreviations like BBC or NASA.
Small capitals are appropriate for: abbreviations within body text, running headers set in capitals, author names in bylines, labels and section markers, and the first line of a chapter opening — a classical typographic convention in which the first few words after a drop cap are set in small capitals to provide a graceful transition from the decorative initial into the body text.
· · ·Kerning and stylistic sets The invisible and the optional
Kerning — the adjustment of space between specific pairs of letters whose natural spacing would otherwise produce awkward gaps — is handled automatically by most modern browsers when the typeface includes kerning tables, as all well-made OpenType fonts do. The CSS property font-kerning: auto is the default, and it enables the typeface's own kerning data. Explicitly setting font-feature-settings: "kern" 1 ensures kerning is active even in environments where it might otherwise be suppressed. For a Paged.js document, this explicit declaration is a safeguard worth keeping.
Stylistic sets are collections of alternate letterforms bundled within a typeface and accessible through OpenType feature tags ss01 through ss20. Each typeface defines its own stylistic sets — there is no universal standard for what ss01 means. EB Garamond, for example, includes stylistic sets that affect the shape of specific letters, providing alternate forms for characters like the lowercase g, the ampersand, and certain ligatures. To discover what stylistic sets a typeface offers, you need to consult the typeface's documentation or specimen, or use a font inspection tool.1
In CSS, a stylistic set is activated with font-feature-settings: "ss01" 1 — or the higher-level font-variant-alternates: stylistic(ss01) if the font has registered the feature names. For most practical work, the font-feature-settings approach is more widely supported and requires no additional registration.
Resolves collisions between the hook of f and adjacent letters. Should be on for all body text.
Numerals that integrate with lowercase text. Essential for body text containing numbers.
True small capitals drawn at x-height. Correct for abbreviations, labels, and chapter openings.
Automatic substitutions based on surrounding characters. Enables discretionary ligatures and contextual forms.
Adjusts spacing between specific letter pairs. Should always be on. Most visible at display sizes.
87,609
4,100
87,609
4,100
Fixed-width numerals for tables and financial data. Essential wherever numbers must align vertically.
Figure 10.2 — OpenType features in EB Garamond. Six features demonstrated side by side, off and on. Each pair uses the same typeface and size; what changes is which glyphs are rendered. Standard ligatures, old-style numerals, small capitals, contextual alternates, kerning, and tabular numerals — these six features, applied consistently, account for most of the visible difference between workmanlike and refined typesetting.
The font-variant shorthand Cleaner syntax for common features
The CSS font-feature-settings property is powerful but low-level. Its syntax — a comma-separated list of four-character feature tags and on/off integers — is not readable at a glance, and it has an important limitation: because it applies all features as a single value, it is difficult to layer in a cascade. Setting font-feature-settings: "kern" 1 on the body and then font-feature-settings: "tnum" 1 on a table cell will disable kerning for that table cell, because the second declaration replaces the first entirely.
The higher-level font-variant properties — font-variant-ligatures, font-variant-numeric, font-variant-caps, font-variant-alternates — do not have this limitation. Each controls a specific aspect of font variation independently, so they compose cleanly in the cascade. Setting font-variant-numeric: tabular-nums on a table cell inherits all other font variant settings from the parent and adds tabular numerals, rather than resetting everything.
For a document stylesheet, the recommended approach is to use font-feature-settings for the base declaration — where you want explicit, low-level control over the foundational set of features — and font-variant properties for context-specific overrides. The base declaration on the body establishes the document-wide defaults; the overrides handle specific elements:
/* ── Base: document-wide OpenType features ── */
body {
/* kern: kerning on, liga: ligatures on,
onum: old-style numerals, calt: contextual
alternates */
font-feature-settings:
"kern" 1, "liga" 1, "onum" 1, "calt" 1;
}
/* ── Running headers: small caps + tracking ── */
.running-header {
font-variant-caps: all-small-caps;
letter-spacing: 0.08em;
}
/* ── Tables: tabular lining numerals ──────── */
table {
font-variant-numeric: lining-nums tabular-nums;
}
/* ── Chapter opener: first line as small caps */
.chapter-body > p:first-of-type::first-line {
font-variant-caps: all-small-caps;
letter-spacing: 0.06em;
}
/* ── Footnote markers: superior figures ───── */
sup {
font-variant-position: super;
/* Uses designed superiors rather than
scaled regular numerals */
}
· · ·
Variable fonts A single file, an infinite range
Variable fonts are a relatively recent addition to the OpenType specification — introduced in 2016 — that allow a single font file to contain not just one typeface but a continuous range of variations along one or more design axes. The most common axis is weight: a variable weight font contains every possible weight from hairline to ultra-black, not as separate files but as a single mathematical description that can be rendered at any point along the spectrum.
Other common axes include width (condensed to expanded), optical size (caption to display), italic (upright to italic or even slanted), and custom axes defined by individual type designers. Some variable fonts offer only one axis; others offer several, allowing extraordinary control over the typeface's appearance.
For document design, variable fonts offer several practical advantages. A single font file replaces what might previously have been four or six separate files — regular, italic, bold, bold italic, light, and so on — reducing load time and simplifying management. More interestingly, they allow intermediate weights that don't exist as named styles in a static font. You can set a subheading at weight 550 — heavier than regular but lighter than semibold — if the visual result is better than either named weight. This kind of fine-grained control is genuinely useful for producing hierarchy that feels exactly right rather than approximately right.
In CSS, variable font axes are controlled with font-variation-settings. For the weight axis, the higher-level font-weight property now accepts any numeric value when the font is variable — font-weight: 550 works on a variable font, not just on one with a named 550 weight. Other axes use four-character tags: font-variation-settings: "opsz" 14 for optical size at 14pt, for example.2
For the project document, EB Garamond is available as a variable font from Google Fonts, requested with the wght axis range in the URL: family=EB+Garamond:ital,wght@0,400..800;1,400..800. This loads a single file covering the full weight range for both roman and italic, replacing what would otherwise be four separate files. The stylesheet references weights numerically, and the variable font renders each one precisely.
Applying features to the project The complete OpenType setup
With the theory in place, here is the complete OpenType configuration for The Compositor's Garden. This block extends the base stylesheet from Chapter 9 with all the typographic refinements covered in this chapter:
/* ════════════════════════════════════════════
OpenType features — The Compositor's Garden
════════════════════════════════════════════ */
body {
/* Foundation: kerning, ligatures,
old-style numerals, contextual alts */
font-feature-settings:
"kern" 1,
"liga" 1,
"clig" 1,
"onum" 1,
"calt" 1;
font-kerning: auto;
}
/* Running headers and labels */
.running-header,
.chapter-label,
.folio,
.opener-label {
font-variant-caps: all-small-caps;
font-variant-numeric: lining-nums;
letter-spacing: 0.1em;
}
/* Chapter opener: first line in small caps */
.chapter-body > p:first-of-type::first-line {
font-variant-caps: all-small-caps;
letter-spacing: 0.06em;
}
/* Footnote superscripts: designed superiors */
sup.fn {
font-variant-position: super;
font-size: inherit;
vertical-align: inherit;
color: var(--accent);
}
/* Tables: tabular lining for data alignment */
table, .data-table {
font-variant-numeric: lining-nums tabular-nums;
}
/* Variable font weight range */
/* Google Fonts URL loads wght@400..800 */
.chapter-title { font-weight: 400; }
h2 { font-weight: 400; }
strong { font-weight: 500; }
.chapter-label { font-weight: 500; }
What this looks like
The chapter you are reading right now has all of these features applied. The numerals in the footnotes are designed superiors. The running header and chapter label use small capitals. The body text uses old-style numerals, ligatures, and kerning throughout. The difference from an unstyled version of the same text is not dramatic — these features do not call attention to themselves. They simply make the text feel complete.
That is the correct test for OpenType features: not "can I see them?" but "would I notice if they were missing?" For most readers, the answer to the first question is no, and to the second question yes — as a vague sense that something is slightly less refined. Invisibly present, visibly absent. That is the mark of features doing their job.
With the OpenType layer in place, the typographic work of Part Two is complete. The project document now has its typefaces, its scale, its spacing system, and its full set of typographic refinements. What it does not yet have is a page — a spatial structure that gives the text its physical dimensions, its margins, and its position on a printed surface. That is the work of Part Three.
Part Three begins with Chapter 11 and the question that underlies all of grid and layout: why do grids exist, and what problem are they solving? The answer is more interesting than it first appears.