Web fonts explained: TTF, OTF, WOFF and WOFF2, and how to ship fonts that load fast

Fonts are the heaviest thing on a lot of pages after images, and the easiest to get wrong in ways that show. Ship the wrong format and you send two or three times the bytes you need to. Ship the whole Unicode range and you send ten times. Configure loading badly and visitors get a second of invisible text, or a jarring reflow as the page repaints in a different typeface. This guide covers what each font format actually is, why WOFF2 is now the only one worth serving, how subsetting and font-display change the numbers, what preloading does and does not buy you, the licensing question people discover too late, and how to convert a font properly with the font converter here.

The formats, and which are still alive

FormatWhat it isUse on the web?
TTFTrueType — quadratic outlines in the SFNT container. The desktop workhorseOnly as a source file. Uncompressed, so it wastes bandwidth
OTFOpenType — usually cubic (CFF) outlines, richer typographic featuresSame: a source format, not a delivery format
WOFFThe same font data wrapped and compressed with zlib, plus metadataSuperseded. Only for a legacy browser matrix you can prove you need
WOFF2Brotli compression plus transforms designed for font tablesYes — this is the one. Universally supported
EOTEmbedded OpenType — Microsoft’s format for Internet ExplorerNo. IE is gone; EOT went with it
SVG fontGlyphs as SVG paths, from the early iOS eraNo. Removed from browsers years ago

TTF and OTF are not really different containers — both are SFNT files, and the difference is mostly which kind of curve the outlines use and which optional tables are present. That is why a converter can read either and produce either: it is repackaging tables, not redrawing glyphs.

So the entire modern declaration is one line of sources:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

If you find yourself pasting a five-format src stack copied from a 2013 blog post, delete everything but the WOFF2 line. Each extra entry is a file you have to generate, host and keep in sync, for browsers nobody is using.

Subsetting is where the bytes actually are

A full Unicode font can carry thousands of glyphs — Cyrillic, Greek, Vietnamese, symbols — and an English-language site renders perhaps a hundred of them. Cutting the file down to the characters you need is by far the biggest win available, usually several times larger than the difference between formats.

Two ways to do it:

  • Generate a subset file with a tool such as pyftsubset (from fontTools), specifying the Unicode ranges or the exact characters. This gives you one small file and total control. For a landing page whose headline never changes, you can subset to just the characters in that headline.
  • Declare unicode-range across several @font-face rules pointing at per-script files. The browser then downloads only the ranges the page actually uses — this is exactly what Google Fonts does when it serves a stylesheet with a dozen small rules.

One caution: subsetting is destructive by design. Cut too aggressively and a user’s name renders in the fallback font, or a currency symbol turns into a box. Keep the punctuation, the currency symbols you might use, and the accented characters your audience’s names contain.

Loading: FOIT, FOUT and layout shift

While a web font is downloading the browser has to decide what to paint, and font-display is how you tell it:

  • swap — paint the fallback immediately, swap when the font arrives. Text is always readable; the cost is a visible reflow. The right default for body copy.
  • block — hide the text for up to about three seconds waiting for the font. This is the flash of invisible text people complain about. Avoid.
  • fallback — a very short block, then fallback, and a swap only if the font arrives within about three seconds.
  • optional — roughly 100 ms to arrive, otherwise the page uses the fallback and does not swap at all for that view. No layout shift, at the cost of the font sometimes not appearing. Ideal for decorative faces.

The reflow that swapcauses is measurable as Cumulative Layout Shift, and there is a good fix that is still under-used: match the fallback’s metrics to the web font with size-adjust, ascent-override and descent-override on a @font-face rule for the local fallback. Tuned properly, the swap becomes nearly invisible because both fonts occupy the same space.

Preload, self-host, and the cache myth

A font is discovered late: the browser must fetch the CSS, parse it, match a rule to text on the page, and only then request the file. rel="preload" short-circuits that for the one or two faces used above the fold:

<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>

The crossorigin attribute is required even for a same-origin file, because fonts are always fetched in CORS mode — omit it and the browser downloads the file twice. And preload only the faces you genuinely need immediately; preloading six fonts makes everything slower by competing with the CSS and the images.

On hosting: the old reason to use a font CDN was shared caching — a visitor arriving from another site that used the same font would already have it. Browsers now partition their HTTP cache by site specifically to close that privacy hole, so the shared-cache benefit no longer exists. Self-hosting removes a DNS lookup and a TLS handshake from the critical path, lets you preload the exact file, and keeps your visitors’ requests off a third party — the last point being why a number of European organisations are required to self-host rather than embed a font service.

Variable fonts

A variable font packs a continuous design space into one file: a wght axis from 100 to 900, often slnt or opsz too. The trade is straightforward. One variable file is bigger than one static weight and smaller than three or four of them, so the break-even is around three weights. If your design uses regular, semibold and bold, and maybe an italic, a variable font is usually the smaller total download and lets you use intermediate weights you did not ship. If you use exactly one weight, ship that one static file.

Licensing, before you convert

This is the part that catches teams out, and it has nothing to do with technology. Font licences are typically sold per use case: a desktop licence covers installing the font on machines, a webfont licence covers serving it from a website and is often priced by monthly page views, and an app or ebook licence is separate again. Converting a TTF you legitimately own into WOFF2 does not extend the licence — if the EULA does not cover web use, the converted file is not licensed for web use either.

Open licences are the easy path: fonts under the SIL Open Font License — which covers most of Google Fonts — can be used, converted, subsetted and self-hosted freely, with the main condition being that you do not sell the font itself and that a renamed derivative keeps the licence. Check the licence file that came with the font. It takes a minute and it is the only step in this guide with a legal consequence.

Converting with the tool

Drop a TTF, OTF, WOFF, WOFF2 or EOT into the font converterand it parses the file in your browser, reports the family name and glyph count, and renders a live preview using the browser’s own font engine — so you can confirm you have the face you think you have before converting anything. Choose an output of WOFF2, WOFF, TTF, EOT or SVG and download the result.

Two implementation details worth knowing. WOFF2 encoding and decoding run in an isolated sandbox page, because the reference WOFF2 build needs a JavaScript capability the rest of the site deliberately forbids — quarantining it keeps the main page’s security policy strict. And when the input is already a TrueType file, the original bytes are fed to the encoder rather than a re-serialised copy, which is both faster and avoids a class of failure where a rebuilt font upsets the encoder.

The reason a font converter belongs in the browser rather than on a server is the licensing point above: a licensed font file is a purchased asset, and uploading it to an unknown web service is exactly the kind of redistribution a foundry’s agreement is written to prevent. Here the file never leaves your machine.

Do this

  • Serve WOFF2 only. Delete the TTF, EOT and SVG entries from your @font-face stack.
  • Subset before you optimise anything else — it saves more than the format choice.
  • Use font-display: swap for text and optional for decorative faces; never block.
  • Preload only the one or two faces used above the fold, and include crossorigin.
  • Self-host: the shared-cache argument for font CDNs no longer holds.
  • Consider a variable font once you need three or more weights.
  • Match fallback metrics with size-adjust to stop the swap shifting your layout.
  • Read the licence before converting a font you bought for desktop use.

Frequently asked questions

Do I still need WOFF, TTF and EOT alongside WOFF2?

No. Every browser in current use supports WOFF2, so a single src entry is enough. EOT existed only for Internet Explorer, SVG fonts were removed from browsers years ago, and shipping raw TTF or OTF to the web means serving an uncompressed font for no benefit. One WOFF2 per face is the modern answer.

How much smaller is WOFF2 than the original font?

Typically 30–50% smaller than the same font as WOFF, and much smaller than a raw TTF, because WOFF2 uses Brotli compression plus transforms specific to font tables. Subsetting saves far more than the container choice does, though: cutting a full Unicode font down to the Latin characters a site actually uses routinely takes a 300 KB file under 30 KB.

Which font-display value should I use?

swap for body text: the page renders immediately in a fallback and swaps when the font arrives, so nothing is invisible. optional for decorative faces, which gives the font 100 ms to load and otherwise skips it entirely for that page view. Avoid block, which is the invisible-text behaviour people complain about.

Is self-hosting fonts faster than using Google Fonts?

Usually, now. The old argument was that a visitor would already have the file cached from another site, but browsers partition their cache per site, so that sharing no longer happens. Self-hosting removes a DNS lookup, a TLS handshake and a third-party dependency, lets you preload the exact file, and keeps visitor requests off another company’s servers — which is also why some European sites are required to self-host.

Can I convert a font I bought for desktop use into a web font?

Technically yes, legally often no. Desktop licences frequently cover installing the font on a machine and nothing else; web use is sold as a separate licence, sometimes priced on page views. Converting the file does not change what you are licensed to do with it. Check the EULA before you convert, not after you ship.

Tools used in this guide

Every one of these runs in your browser — the files you work on never leave your device.

More developer guides