CSS Font Face
The @font-face rule lets you load and use custom font files that you host yourself. Instead of relying on Google Fonts or system fonts, you provide the font files directly — giving you full control over which fonts load, how they load, and how they look, even offline.
Why Self-Host Fonts
- Works without internet access (intranet sites, offline apps)
- No third-party dependency — no Google, no external requests
- Full privacy — Google Fonts can log user IPs; self-hosted fonts cannot
- Custom or licensed fonts not available on Google Fonts
- Slightly faster in some setups because the font is on the same server as your site
Basic @font-face Syntax
@font-face{
font-family: "MyFont"; /* Name you'll use in CSS */
src: url("fonts/myfont.woff2") format("woff2"),
url("fonts/myfont.woff") format("woff");
font-weight: normal; /* 400 */
font-style: normal;
font-display: swap;
}
After declaring it, use the name just like any other font:
body {
font-family: "MyFont", sans-serif;
}
Font File Formats
DIAGRAM — Font format support:
Format │ Extension │ Support
──────────┼───────────┼─────────────────────
WOFF2 │ .woff2 │ All modern browsers ← Use this first
WOFF │ .woff │ All modern browsers ← Fallback
TTF/OTF │ .ttf/.otf │ Most browsers (larger file)
SVG │ .svg │ Old iOS Safari only (avoid)
EOT │ .eot │ Old IE only (avoid)
Always provide WOFF2 first — it is the most compressed format, so pages load faster. WOFF as a fallback covers any remaining browsers. TTF/OTF is a safe last resort since file sizes are larger.
src Descriptor — Format Stack
The src descriptor lists font sources in order. The browser downloads the first format it supports.
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("fonts/brand.woff2") format("woff2"),
url("fonts/brand.woff") format("woff"),
url("fonts/brand.ttf") format("truetype");
}
DIAGRAM — Browser picks the best supported format:
Browser checks: woff2 supported? → YES → downloads brand.woff2, stops.
woff2 supported? → NO → woff supported? → YES → uses woff.
→ NO → tries ttf.
local() — Check System First
Add local() before the URL sources to check if the user's computer already has the font installed. If it does, the browser skips the download entirely.
@font-face{ font-display:swap;
font-family: "Inter";
src: local("Inter"),
local("Inter-Regular"),
url("fonts/Inter.woff2") format("woff2");
}
DIAGRAM — local() shortcut:
User has Inter installed? → YES → use system copy (no download)
→ NO → download from server
Loading Multiple Weights
Each font weight and style requires a separate @font-face block. Use the same font-family name but different font-weight values.
/* Regular */
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("fonts/brand-regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
}
/* Bold */
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("fonts/brand-bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
}
/* Italic */
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("fonts/brand-italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
}
/* All three variants now work under one name */
h1 { font-family: "BrandFont"; font-weight: 700; }
p { font-family: "BrandFont"; font-weight: 400; }
em { font-family: "BrandFont"; font-style: italic; }
Variable Fonts — One File, All Weights
A variable font stores the entire weight range in a single file. This reduces HTTP requests from many to one.
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("fonts/brand-variable.woff2") format("woff2-variations");
font-weight: 100 900; /* range this file covers */
font-style: normal;
}
/* Now any weight from 100 to 900 works */
h1 { font-weight: 800; }
p { font-weight: 350; } /* intermediate weights too */
font-display — Control Loading Behavior
DIAGRAM — font-display values and behavior:
swap → Text visible immediately in fallback font, swaps when loaded
✅ Best for body text — readability first
block → Text invisible for up to 3s, then shows custom or fallback
⚠ Can show blank text — use sparingly for decorative fonts
fallback → Very short invisible period, then fallback. Swaps only if
font loads within ~3s.
✅ Good balance for performance
optional → Short invisible period. If font not ready, use fallback
permanently. No swap.
✅ Best for performance — font may not show on slow connections
auto → Browser decides (usually same as block)
@font-face{
font-family: "BrandFont";
src: url("fonts/brand.woff2") format("woff2");
font-display: swap; /* recommended for most cases */
}
Recommended File Folder Structure
project/
├── css/
│ └── style.css
└── fonts/
├── brand-regular.woff2
├── brand-regular.woff
├── brand-bold.woff2
└── brand-bold.woff
/* In style.css — path is relative to the CSS file */
@font-face{ font-display:swap;
font-family: "BrandFont";
src: url("../fonts/brand-regular.woff2") format("woff2");
font-weight: 400;
}
Converting Font Formats
If you only have a TTF or OTF file, convert it to WOFF2 and WOFF using free tools like Fontsquirrel's Webfont Generator or the woff2 command-line tool. Always convert legally licensed fonts only.
@font-face vs Google Fonts
| Feature | @font-face (self-hosted) | Google Fonts |
|---|---|---|
| Works offline | Yes | No |
| Privacy | Full — no third-party requests | Google may log requests |
| Custom/licensed fonts | Yes | Only free Google fonts |
| Setup effort | Medium | Very easy |
| CDN delivery | Your server | Google's global CDN |
Self-hosting fonts with @font-face is the professional approach for branded projects with custom typefaces or strict privacy requirements. Use WOFF2 as your primary format, include a WOFF fallback, and always set font-display: swap to keep text visible while fonts load.
