Skip to content

Font

a typeface in a file the page downloads

What it is

A font, called a webfont when a site serves it and a font face in the CSS specifications, is the file holding the drawn letters of one design in one weight and style, along with the tables that tell software which drawing stands for which character and how to space them.

Typographers keep the design and its files apart. Ubuntu is a typeface; Ubuntu Light and Ubuntu Bold Italic are fonts of it. CSS keeps the same split. A font family groups faces under one name, and each @font-face rule describes a single face: the family it joins, where to fetch it, the weight and style it covers, the characters it can draw and what to show while it loads.

@font-face {
    font-family: "Reading Serif";
    src:
        local("Reading Serif Regular"),
        local("ReadingSerif-Regular"),
        url("/fonts/reading-serif-400.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

body {
    font-family: "Reading Serif", Georgia, serif;
}

The font-family property on an element is a list read from left to right. The specification chooses a family and a face for each character in a run of text, taking the first face that matches the requested weight and style and contains a glyph for that character. One line can therefore be set in two fonts when the first lacks a letter, and the generic name at the end of the list, serif, sans-serif, monospace or system-ui, means some installed font always takes the remainder.

On the web the file is almost always WOFF2: the same OpenType or TrueType outlines and tables, repackaged for transfer and compressed with Brotli. Inside, the cmap table maps each character code to a glyph, and other tables carry the numbers a layout engine needs, such as ascent, descent, line gap and where an underline sits and how thick it is. Those numbers decide line height and spacing as much as the letterforms decide the look.

Why it matters

Every weight and style is a separate download. A design using regular, italic and bold puts three files between a reader and the text set in them, and each one adds bytes to the first visit. The unicode-range descriptor lets a family be split into pieces by script, and the specification forbids a browser from downloading a piece for characters outside its range, so a page fetches only the pieces its text uses.

Text in a webfont cannot be drawn before the file arrives, so the browser needs a policy for the wait. The font-display descriptor sets it. With block, the specification recommends a block period of 3s, during which the text is laid out but drawn invisibly. With swap, the block period is 100 ms or less, the fallback font appears almost at once, and the webfont replaces it whenever it lands. With fallback, the swap window is short, with 3s recommended, after which the page keeps the fallback. With auto, the browser decides, and many treat it like block.

Where the file lives matters too. Font requests use CORS, so a font on another origin loads only when that server allows it, and every request to another company’s server tells it the reader’s address and which site they are on. Serving fonts from the site’s own host removes both the extra connection and the observer.

A font is software under a licence. The SIL Open Font License allows use, study, modification and redistribution as long as the fonts are not sold by themselves, and bars a modified version from using a reserved font name without permission. A font bought for print may carry different terms for the web, so read the licence before the file goes on a server.

Coverage is the last concern. A font draws only the characters its cmap lists. Most of the Cyrillic alphabet sits in the run U+0400–045F, but the Ukrainian letter ґ lives at U+0490–0491, so a subset or a range written for Russian text leaves it to whatever fallback comes next.

What it looks like in practice

The site’s credits page lists what reaches a reader’s browser as five things, and three of them are webfonts. Ubuntu, by Dalton Maag for Canonical, is under the Ubuntu Font Licence. Ubuntu Mono, the same family’s fixed-width cut, sets the code. Shantell Sans, based on the artist Shantell Martin’s felt-marker handwriting, is under the SIL Open Font License and writes the margin notes. All three come from the site’s own machine, and the page says why: a webfont from someone else’s CDN is a third party watching you read. Ubuntu Mono and Shantell Sans load only on the pages that carry code or a margin note.

Splitting one typeface by script is what the Safari underline note is about. The site serves Ubuntu in six pieces, three weights of Latin and three of Cyrillic, each declaring the characters it covers, so English and Danish pages fetch three Latin files and no Cyrillic ones, and Ukrainian pages the reverse. All six shared one family name, and in Safari a face with a non-Latin range degrades skip-ink, the gap an underline leaves around a descender, for every character in its family, Latin text included. Deleting the three Cyrillic declarations at runtime took the measured gaps from 1.8/1.0/1.0px to 4.5/11.0/5.0px. The fix gave the Cyrillic faces a family name of their own and put it in the stack after Ubuntu: three renamed declarations and one stack entry, with Chrome untouched and Ukrainian still set in Ubuntu at identical widths. The same note shows how much a font’s own numbers vary: Ubuntu declares an underline thickness of 0.056em for Light, 0.020em for Medium and 0.120em for Bold.

The landing page in front of the company’s own git forge had been a hand-maintained copy of the main site’s design, and every difference found between the two was an accident rather than a decision. Two of the five were about fonts: a font declaration that collapsed two weights to one on any machine with the family installed, and a weight used with no font shipped for it. None of the five was visible in a screenshot. The copy was deleted, and the landing page is now built by the main site’s own templates.

Where it goes wrong

  • Text stays invisible while the file loads. Under block, and often under auto, a slow connection shows a laid-out page with no words in it for up to the block period. Use swap for body text and preload the one or two files the first screen needs. A font preload needs the crossorigin attribute even for a same-origin file, because the preload has to match the CORS request it stands in for.
  • The page jumps when the font arrives. A fallback with different widths and line heights breaks lines in other places, and the swap reflows them. The specification advises fallbacks whose metrics closely match the webfont. Where none does, a local() face for the fallback with ascent-override, descent-override and line-gap-override brings its vertical metrics into line, so the swap changes letterforms rather than layout.
  • Bold and italic nobody drew. When a family has no bold face, the browser may thicken the regular one, and it may slant the regular one when there is no italic. Strokes smear and roman letters lean. Ship every face the design uses, or set font-synthesis to none so a missing face shows up in review as upright, regular-weight text rather than a fake.
  • local() takes a face, not a family. The name inside local() matches a single face by its full name or its PostScript name. Writing the family name there for every weight can match one installed face for all of them, so bold renders as regular on exactly the machines that have the family installed. Name each face, both ways, or leave local() out.
  • A missing glyph mixes typefaces. A face that lacks a character hands it to the next family, so one word can show two designs. A subset cut too tightly does the same for a whole script. Check coverage against the text the site actually publishes, in every language it publishes.

Further reading

Glossary