Type something to search...
What are web fonts and how do they work?

What are web fonts and how do they work?

Web fonts are font files that a browser downloads from a server so a page can display text in a typeface the visitor doesn't have installed. You declare them in CSS with the @font-face rule, giving each face a family name, a file URL and descriptors such as weight and style, and then use that family name in font-family like any other font. When the browser finds text that needs the font, it downloads the file, usually in the compressed WOFF2 format, and renders the text with it. Until the file arrives, the browser either hides the text briefly or shows it in a fallback font, depending on the font-display setting. Services such as Google Fonts and Adobe Fonts wrap this process for you, but underneath it is always the same @font-face mechanism.

Before web fonts were widely supported, designers were limited to a handful of fonts installed on most computers, or had to put text in images. Today almost every site uses at least one web font, and understanding how they work helps you choose them, load them quickly and avoid problems like invisible text and layout shift. In this article you'll learn what happens from CSS to rendered text, the role of each @font-face descriptor, which file formats matter, how hosted services work, how to control loading with CSS and JavaScript, and what web fonts cost in performance and privacy.

Web Fonts vs Installed Fonts

When you set font-family: Georgia, the browser looks for Georgia among the fonts installed on the visitor's device. If it's missing, the browser moves to the next family in the list. Fonts like this are often called web-safe or system fonts.

A web font works differently. You supply the font file, so every visitor sees the same typeface whatever device they use:

Installed fontWeb font
Where it comes fromThe visitor's operating systemYour server or a font service
Download neededNoYes, on first use
Same on every deviceNo, depends on what's installedYes, if it loads
LicenceCovered by the operating systemMust allow web embedding
Performance costNoneFile size, extra requests, possible layout shift

How a Web Font Gets From Server to Screen

Here's what happens on a typical page load:

  1. The browser parses CSS and records each @font-face rule. Declaring a font doesn't download it.
  2. It builds the page layout and works out which font each piece of text needs, based on font-family, font-weight, font-style and the characters in the text.
  3. It matches a face: For each piece of text it finds the @font-face rule whose family, weight, style and unicode-range best fit.
  4. It downloads only the faces it needs: If no text on the page uses the bold italic, that file is never fetched.
  5. It renders text while waiting: Depending on font-display, text is hidden for a short block period or shown in the fallback font.
  6. It swaps in the web font: When the file arrives, text re-renders in the web font, which can cause a visible change and possibly layout shift.
  7. It caches the file: Later pages on the same site reuse the cached file, so the cost is mostly paid once.

Because the download only starts once the browser knows the font is needed, which is after CSS is downloaded and parsed, fonts are often among the later resources to arrive. That's why preloading and careful fallbacks matter.

The @font-face Rule

A complete declaration looks like this:

@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-variable.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC,
    U+2000-206F, U+20AC, U+2122, U+2212;
}

body {
  font-family: "Literata", Georgia, "Times New Roman", serif;
}

Each descriptor has a specific job:

  • font-family: The name you'll use in your CSS. It doesn't have to match the font's internal name, though keeping them aligned avoids confusion.
  • src: Where to get the font. You can list several sources separated by commas, and the browser uses the first it can load. local() sources refer to an installed font by name.
  • font-weight: Which weight this file provides. A range such as 200 900 tells the browser a variable font covers all those weights.
  • font-style: normal or italic. Italic is usually a separate file, declared with its own @font-face using the same family name.
  • font-display: How text behaves while the font is loading.
  • unicode-range: Which characters this file covers. The browser only downloads it if the page contains characters in that range.

The @font-face rule is covered in more depth in how to use the @font-face rule.

Grouping Faces Into One Family

You declare multiple files under the same font-family name, and the browser picks the right one for each element:

@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-variable.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-variable-italic.woff2") format("woff2");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

Now em elements automatically use the italic file, and strong uses the bold region of the variable font, with no extra family names needed.

File Formats

Several font formats exist, but for the web today one matters most.

  • WOFF2: The standard web font format, compressed with Brotli. Supported by all current browsers and usually the smallest. Use it by default.
  • WOFF: The earlier web format with weaker compression. Only needed for very old browsers.
  • TTF and OTF: Desktop font formats. Browsers can use them, but the files are larger and uncompressed.
  • EOT and SVG fonts: Obsolete formats for old Internet Explorer and early iOS. You don't need them.

For modern sites, a single WOFF2 source is enough.

Hosted Font Services

Services such as Google Fonts and Adobe Fonts host the files and generate the @font-face rules for you. With Google Fonts you add a stylesheet link:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Literata:ital,opsz,wght@0,7..72,400;0,7..72,700;1,7..72,400&display=swap"
>

That stylesheet contains @font-face rules pointing to files on fonts.gstatic.com, typically split by unicode-range into subsets such as Latin, Latin Extended, Cyrillic and Vietnamese. A page in English only downloads the Latin file.

You can see what a service returns with curl. The response varies by browser, so send a modern user agent:

curl -s -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" \
  "https://fonts.googleapis.com/css2?family=Literata&display=swap" | head -n 12
/* cyrillic-ext */
@font-face {
  font-family: 'Literata';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/literata/v...) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Literata';

The exact URLs and ranges change as the service updates fonts, but the structure is the same.

Hosted or Self-Hosted?

Browsers now partition their HTTP cache by site, so a font cached from Google Fonts on one site isn't reused by another site. That removed the old argument that shared hosting saves downloads. Self-hosting puts the files on your own domain, avoids a connection to a third party and gives you full control over caching and subsetting. Hosted services are quicker to set up and handle subsetting and format updates for you.

Self-hosted fonts loaded from a different origin, such as a CDN subdomain, need a CORS header, because browsers fetch fonts in CORS mode:

Access-Control-Allow-Origin: https://www.example.com

Controlling Font Loading

font-display

The font-display descriptor controls what visitors see while a font downloads:

  • auto: The browser's default, usually similar to block.
  • block: Hides text for up to about three seconds, then shows the fallback until the font arrives.
  • swap: Shows the fallback immediately and swaps to the web font whenever it loads.
  • fallback: A very short block, then the fallback; the web font is only used if it arrives within a few seconds.
  • optional: A very short block; the browser may decide not to use the web font at all on that visit if it isn't available almost immediately.

swap is a common choice for body text, and optional is useful when avoiding layout shift matters more than showing the web font on a first visit.

Preloading

Because the browser discovers fonts late, you can tell it about a critical font up front:

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

The crossorigin attribute is required even for same-origin fonts, because fonts are fetched in CORS mode. Preload only one or two files that are used above the fold, or you'll compete with more important resources.

The CSS Font Loading API

JavaScript can observe and control fonts through document.fonts:

// Wait until every font used so far has loaded
await document.fonts.ready;
console.log("Fonts ready:", document.fonts.status);

// Load a specific face on demand
const [face] = await document.fonts.load('italic 400 1rem "Literata"');
console.log(face ? `${face.family} ${face.style} loaded` : "No matching face");

// Check whether a face is available without triggering a download
console.log(document.fonts.check('700 1rem "Literata"'));
Fonts ready: loaded
Literata italic loaded
true

You can also create a font entirely in JavaScript, which is useful for fonts that are only needed after a user action:

const display = new FontFace("Literata Display", "url(/fonts/literata-display.woff2)", {
  weight: "700",
  display: "swap",
});

document.fonts.add(display);
await display.load();
document.documentElement.classList.add("display-font-loaded");

Fallbacks Still Matter

Every web font needs a fallback stack, because fonts can be slow, blocked by extensions or corporate networks, or fail to load entirely. List fonts with similar proportions, then a generic family:

body {
  font-family: "Literata", Georgia, Cambria, "Times New Roman", serif;
}

To reduce layout shift when the web font swaps in, you can define a metric-adjusted fallback with size-adjust, ascent-override and descent-override, so the fallback occupies the same space as the web font.

What Web Fonts Cost

  • Bytes: A typical WOFF2 Latin subset is tens of kilobytes per style. Several styles from several families add up fast.
  • Time: Fonts are discovered late and can delay text rendering or cause it to change after it first appears.
  • Layout stability: Swapping from fallback to web font can move text, contributing to Cumulative Layout Shift.
  • Privacy: Loading fonts from a third-party service sends the visitor's IP address and the page's referrer to that service. Some organisations self-host for this reason.
  • Licensing: The font's licence must allow web embedding, and commercial fonts are often licensed by page views.

None of these are reasons to avoid web fonts. They're reasons to use fewer of them, in WOFF2, with sensible font-display values and fallbacks.


FAQ: Web Fonts

They are often the same typeface. A web font is a font file served from a server and loaded with the @font-face rule, while a regular font is installed on the device. Web fonts usually use the compressed WOFF2 format and need a licence that allows web embedding.

WOFF2. All current browsers support it and it has the best compression. You only need WOFF or TTF if you must support very old browsers.

They add downloads and can delay or shift text, but the impact is small if you limit families and weights, use WOFF2, preload the most important file and set an appropriate font-display value.

No. Declaring a font with @font-face doesn't trigger a download. The browser only fetches a face when text on the page actually uses it, and only if the text includes characters in its unicode-range.

Yes. Google Fonts is a hosted service that serves web font files and the CSS @font-face rules for them. You can also download the same fonts and self-host them.

The browser uses the next font in your font-family list. That's why every stack should end with similar installed fonts and a generic family such as serif or sans-serif.


Conclusion

Web fonts let you use almost any typeface on a website by serving the font files yourself or through a service. The @font-face rule describes each face, the browser downloads only the faces it needs when it needs them, and font-display decides what visitors see in the meantime. WOFF2 is the only format most sites need, and hosted services like Google Fonts are a convenient wrapper around the same mechanism.

Used well, web fonts give your site a consistent identity on every device. Keep the number of files small, preload the critical one, choose font-display deliberately, provide a well-matched fallback stack and check the licence. Those few habits cover most of what it takes to make web fonts fast and reliable.

Share :

Related Posts

How to make typography accessible?

How to make typography accessible?

You make typography accessible by choosing clear typefaces, setting text in relative units so it scales with user preferences, giving it enough colou

Dive Deeper
What are the parts of a letterform?

What are the parts of a letterform?

A letterform is the shape of a single letter, and typographers break it down into named parts. The main ones are the stem (the main vertical

Dive Deeper
What are ascenders, descenders and the baseline?

What are ascenders, descenders and the baseline?

The baseline is the invisible line that letters sit on. Ascenders are the parts of lowercase letters that rise above the x-height, such as th

Dive Deeper