Skip to main content
Syllabus

Web Fonts: Google Fonts, @font-face and Variable Fonts

Badar KhalilUpdated September 30, 2026 7 min read

Web Fonts: Google Fonts, @font-face and Variable Fonts

Web fonts let you use any typeface on your site by downloading font files to the visitor browser. They give you consistent branding on every device, but they also cost bandwidth and can cause flashes of unstyled text and layout shift. This lesson shows how to use Google Fonts, write your own @font-face, choose formats, control loading with font-display, and use variable fonts.

What You Will Learn

  • How to add Google Fonts with link and with @import
  • How to write @font-face rules and self-host fonts
  • Font formats: WOFF2 and why it is enough today
  • Loading behavior: FOIT, FOUT and font-display
  • Preloading, subsetting and unicode-range
  • Variable fonts: axes, font-variation-settings and weight ranges
  • Performance, privacy and licensing

Using Google Fonts

Choose a font on Google Fonts, pick the styles you need, and copy the embed code. The recommended pattern uses preconnect hints and a link tag in the head:

<link rel='preconnect' href='https://fonts.googleapis.com'>
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap' rel='stylesheet'>

Then use the font in CSS with a fallback: font-family: 'Inter', system-ui, sans-serif;. The display=swap parameter adds font-display: swap to the generated rules. Prefer the link method over @import, because @import inside CSS delays the download.

Writing @font-face

The @font-face at-rule declares a font family name and tells the browser where to get the file:

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
  • font-family: the name you will use in your CSS. It can be anything, but use a clear name.
  • src: one or more sources. local() tries an installed copy first, url() downloads the file.
  • font-weight and font-style: tell the browser which face this file is. Declare one rule per weight and style you use.
  • font-display: controls what happens while the font loads.
  • unicode-range: download this file only when the page uses characters in the range.

Font Formats

  • WOFF2: compressed, supported by all modern browsers. This is the only format you need for new projects.
  • WOFF: older compressed format, needed only for very old browsers.
  • TTF/OTF: desktop formats, larger files. Convert them to WOFF2.
  • EOT and SVG fonts: obsolete. Do not use them.

Font Loading Behavior: FOIT and FOUT

  • FOIT (flash of invisible text): text is hidden until the font arrives.
  • FOUT (flash of unstyled text): text shows immediately in a fallback and swaps when the web font arrives.

The font-display descriptor picks the behavior:

  • auto: browser default (usually like block).
  • block: short invisible period, then swap. Use for icon fonts.
  • swap: show fallback immediately, swap when ready. Best for body text and most sites.
  • fallback: very short invisible period and a short swap window. Good balance to reduce visible shifts.
  • optional: the font is used only if it loads very quickly, otherwise the fallback stays for that visit. Best for performance and no layout shift.

Preloading Critical Fonts

Fonts are discovered late because the browser must first download CSS. Preload the one or two fonts 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 same-origin fonts. Preload only what you really need, since unused preloads waste bandwidth.

Subsetting and unicode-range

A font file may contain thousands of glyphs. You can subset it to only the languages you need, for example Latin only. Google Fonts does this automatically with several @font-face rules and unicode-range values, so a visitor downloads only the subset their page uses. For Urdu or Arabic pages, make sure the Arabic subset is included.

Variable Fonts

A variable font packs a whole family into one file with continuous axes. Registered axes are weight (wght), width (wdth), slant (slnt), italic (ital) and optical size (opsz). Fonts can also define custom axes in uppercase.

@font-face {
  font-family: 'Inter VF';
  src: url('/fonts/inter-variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}
h1 { font-weight: 850; }
.narrow { font-variation-settings: 'wdth' 80; }
  • Declare the range with font-weight: 100 900 so any number in that range works.
  • Use the normal properties when they exist: font-weight for wght, font-stretch for wdth, font-style: oblique 10deg for slnt, and font-optical-sizing: auto for opsz.
  • Use font-variation-settings only for custom axes, and remember it overrides everything at once, so avoid mixing it with the standard properties.
  • A single variable file is often smaller than three or four static weights, and it enables smooth animation of weight.

Reducing Layout Shift with Fallback Metrics

You can create a local fallback face that matches the web font metrics, so the swap causes almost no shift:

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}
body { font-family: 'Inter', 'Inter Fallback', sans-serif; }

Tools and frameworks can calculate these numbers for you. Modern build tools often generate them automatically.

The CSS Font Loading API

In JavaScript, document.fonts.load('1rem Inter') returns a promise, and document.fonts.ready resolves when all fonts used are loaded. You can use it to add a class like fonts-loaded to the html element.

Performance, Privacy and Licensing

  • Limit families and weights. Every extra file costs time.
  • Self-hosting (download the files and serve them yourself) removes third-party requests, gives you full caching control and avoids sending visitor IP addresses to an external font service. This matters for privacy rules such as GDPR.
  • Check the license. Google Fonts are open source, but commercial fonts need a web license.
  • Serve fonts with long cache headers, for example Cache-Control: public, max-age=31536000, immutable.

Common Mistakes

  • Loading six weights when the design uses two.
  • Forgetting font-display and leaving text invisible on slow connections.
  • Declaring a variable font without a weight range, so only one weight works.
  • Missing crossorigin on a font preload, which makes the browser download the file twice.
  • Using @import for Google Fonts inside a large CSS file.
  • Skipping a fallback stack and generic family.

Frequently Asked Questions

Which font format should I use?

WOFF2 only. It is supported by every current browser and has the best compression.

What does font-display: swap do?

It shows text immediately in a fallback font and replaces it with the web font when it finishes loading.

Should I self-host Google Fonts?

Often yes. It improves control over caching and performance and avoids third-party requests, but you must update the files yourself.

What is a variable font?

A single font file that contains a range of styles, such as every weight from 100 to 900, which you select with normal CSS properties.

Key Takeaways

  • Use WOFF2 with @font-face or the Google Fonts link with display=swap.
  • Always include a fallback stack and set font-display.
  • Preload only critical fonts and add crossorigin.
  • Variable fonts reduce file count and give fine control through font-weight ranges.
  • Match fallback metrics to reduce layout shift.
Try it Yourself CSS
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself HTML
Output

Press Run to execute.

Try it Yourself CSS
Output

Press Run to execute.

Try it Yourself CSS
Output

Press Run to execute.

Try it Yourself CSS
Output

Press Run to execute.

Try it Yourself JAVASCRIPT
Output

Press Run to execute.

Exercise: Write an @font-face RuleCSS

Write an @font-face rule for the family name 'Lora' that loads /fonts/lora-400.woff2 as WOFF2, is weight 400 and normal style, and shows fallback text immediately while loading. Then apply it to the body with a serif fallback.

Try it Yourself CSS
Output

Press Run to execute.

Show expected output
An @font-face rule with src url('/fonts/lora-400.woff2') format('woff2'), font-weight 400, font-style normal and font-display swap; body uses 'Lora', serif.

This is a self-check — compare your result with the expected output above.

Exercise: Variable Font DeclarationCSS

Declare a variable font family 'Roboto Flex VF' from /fonts/roboto-flex.woff2 that supports every weight from 100 to 1000 and uses swap loading. Then style .title with weight 650 and .tag with the custom axis setting for 'wdth' set to 90 using font-variation-settings.

Try it Yourself CSS
Output

Press Run to execute.

Show expected output
@font-face declares font-weight: 100 1000; .title has font-weight: 650; .tag uses font-variation-settings: 'wdth' 90.

This is a self-check — compare your result with the expected output above.

Exercise: Choose a font-display ValueJAVASCRIPT

Write a function fontDisplay(kind) that returns the best font-display value: 'icons' returns 'block', 'body' returns 'swap', 'hero-brand' returns 'fallback', and 'performance-critical' returns 'optional'. For anything else return 'auto'.

Try it Yourself JAVASCRIPT
Output

Press Run to execute.

Show expected output
block
swap
optional
auto

This is a self-check — compare your result with the expected output above.

Was this page helpful?