Earlier pages chose typefaces for what they say. This one follows a font from the @font-face rule that names it, through the file formats and the servers that deliver it, to the moments the browser has to decide what to draw while it waits. Two labs let you watch those decisions, and the site's own type, IBM Plex, serves as the worked example throughout.
1. What a web font costs
Every page has type, but not every page needs to download it. Text set in system-ui, or in Georgia or Arial where they are installed, uses fonts already on the reader's device: no request, no wait, no risk. A web font is one the page brings along, and it buys control over the look of every word at the price of a file that must arrive before those words can be drawn in it.
That price is unusual because of where it falls. An image that arrives late leaves a gap in the layout; the text around it is readable. A font that arrives late affects the text itself, which is usually the reason the reader came. The browser has three choices while it waits, and each costs something:
- Wait, drawing nothing. The reader sees a page of empty lines, and on a slow connection may wait seconds to read a headline that was in the HTML all along.
- Draw a fallback, then swap. The words are readable at once, but they change shape when the font lands, and lines rewrap and push the page down.
- Draw a fallback and keep it. Nothing moves, but this visit never looks as designed.
Most of this page is about choosing among those and making the choice cheaper. The course's rule for images applies unchanged: less data, less often, when needed, from nearby. A font that is not needed is the fastest of all.
2. The @font-face rule
A web font enters a page through an @font-face rule. The rule does not set any text in the font; it describes a font face, gives it a family name, and says where its file is. Text uses it only when some font-family names that family, and the browser downloads the file only when text on the page actually needs it.
The family name is yours to choose: "Inter" here, but "Body text" would work as well, since it only connects this rule to the font-family lists that ask for it. The descriptors fall into three groups.
| Job | Descriptors | What they do |
|---|---|---|
| Naming and finding | font-family, src | The name text will ask for, and a list of places to get the font: url() with an optional format() and tech() hint, or local() for a font already installed |
| Matching | font-weight, font-style, font-stretch (renamed font-width in CSS Fonts 4; use font-stretch for now), unicode-range | Which requests this file answers: a weight of 700 looks for the face whose weight range includes 700; a character looks for the face whose range includes it |
| Behavior | font-display, size-adjust, ascent-override, descent-override, line-gap-override, font-feature-settings, font-variation-settings | What to draw while the file loads, and how to adjust the font once it has |
A family is usually several rules with one name. Two static files for regular and bold are two @font-face rules, both font-family: "Plex", one with font-weight: 400 and one with 700; the browser picks between them by the weight the text asks for. A variable font covers a range with one file, so its rule declares the range, font-weight: 100 900. Variable fonts, page 08 of this section, explains the axes. If no face matches a request for bold or italic, the browser fakes one by smearing or slanting the regular, which is why a missing face shows up as ugly type rather than an error.
A rule that left and came back
@font-face is older than most of CSS. It was in CSS2, a W3C Recommendation in May 1998, at a time when the only browser that implemented @font-face was Internet Explorer, using a format of its own. Other browsers did not follow, and when CSS 2.1 tidied the specification to match what browsers did, its list of changes says that "font descriptors, the '@font-face' declaration, and all associated parts of the font matching algorithm have been removed in CSS 2.1." For a decade, web type meant the handful of fonts on everyone's computer, plus text set in images.
The rule came back with CSS Fonts Level 3 and with browsers that loaded ordinary font files: Safari 3.1 in 2008, then Firefox 3.5 and Chrome in 2009 and 2010. A short history of type tells the longer story, including the image and Flash workarounds of the years between.
3. Font files and formats
A font file is not a picture of letters. It is a small database of tables, each named by a four-letter tag, and the browser consults several of them to draw a single word:
A character is not a glyph (Anatomy and vocabulary), and cmap is where one becomes the other. The outlines in glyf (TrueType's quadratic curves) or CFF (PostScript's cubic ones) are the letters themselves. GSUB and GPOS carry the font's intelligence: ligatures, kerning, and the placement of accents, without which Arabic or Devanagari cannot be set at all. Hinting tables hold small programs that fit outlines to the pixel grid at small sizes. The vertical metrics return in section 10. None of this needs mastering, but it explains why a font file is larger than its letters suggest, and why cutting it down takes a tool.
Five formats in twenty years
The same tables have traveled to browsers in several wrappers, and the history explains a lot of copied CSS that is still around:
| Format | What it was | Who used it | Today |
|---|---|---|---|
| EOT | Embedded OpenType: Microsoft's compressed font, which could carry a list of the sites allowed to use it | Internet Explorer, from version 4 | Dead with Internet Explorer |
| SVG fonts | Glyphs drawn as SVG paths, without hinting or most OpenType features | Safari on the iPhone, which before iOS 4.2 accepted no other web font; Chrome until version 38 removed them | Removed from Chrome; never in Firefox; do not serve |
| TTF / OTF | The desktop font file itself, TrueType or OpenType, uncompressed | Safari 3.1 onward, then every browser | Works, but with no font-specific compression |
| WOFF | Web Open Font Format: the same tables, each compressed with zlib, plus optional metadata. W3C Recommendation, 13 December 2012 | Every browser from about 2011, including Internet Explorer 9 | Supported everywhere, superseded |
| WOFF2 | The same idea with Brotli compression and table transforms that make outlines compress better. W3C Recommendation, 1 March 2018 | Chrome 36, Firefox 39, Safari 10, Edge 14 onward | The one format to serve |
The gains are real. This section's Inter, rewrapped with fonttools for comparison: 168,596 bytes as a raw TrueType file, 88,600 as WOFF, 72,920 as WOFF2, the file the demos load. When every browser in use reads WOFF2, a long list of fallbacks only adds noise, so the classic "bulletproof" syntax from the early 2010s can go:
The ?#iefix trick worked around a parsing bug in Internet Explorer 6 to 8, and the SVG line served iPhones of 2010. A browser too old for WOFF2 still gets the page's text in a fallback font, which is exactly the outcome a font stack ending in a generic family guarantees.
4. Self-hosting or a font service
There are two ways to get a font onto a page. Self-hosting means the WOFF2 files sit on your own server and your CSS has the @font-face rules. A font service, such as Google Fonts or Adobe Fonts, hosts the files and the CSS, and the page links to it:
Three lines and it works: the service picks formats, splits fonts with unicode-range, and keeps up with new versions. The costs are less visible:
- Two more origins. The CSS comes from one host and the fonts from another, and each needs its own DNS lookup, TCP connection and TLS handshake before the first byte. The
preconnecthints start that work early (link relationships), but they cannot make it free. - A render-blocking style sheet you do not control. The service's CSS is small, but the page cannot render until it arrives.
- No shared cache any more. The old argument was that a popular font was already cached from another site. Browsers now keep a separate cache per site, to stop sites from detecting each other through it: Safari since 2013, Chrome since version 86 in 2020, Firefox since version 85 in 2021. Every site downloads its own copy.
- Every visit is reported. On every visit that misses the cache, the service sees the reader's IP address and browser, and, under the default referrer policy, the site's origin.
The privacy question
That point went to court. On 20 January 2022 the Munich Regional Court, in case 3 O 17493/20, ruled against a website owner who embedded Google Fonts from Google's servers without the visitor's consent. The court treated the visitor's dynamic IP address as personal data under the GDPR, held that sending it to Google had no legitimate interest behind it, because the fonts could have been used without contacting Google's servers by hosting them on the site itself, and noted the transfer to a server in the United States. It awarded the visitor €100 in damages and ordered the site to stop passing his IP address to Google.
The sum was small, but the reasoning was not, and a wave of mass demand letters to German site owners followed. The lesson for developers does not depend on the law of one country: a third-party font request is a disclosure of the reader's visit, made by the page on the reader's behalf. Privacy treats this as a quality of the site, not a legal box to tick.
Self-hosting removes the disclosure, the extra origins and the dependency, at the cost of writing the rules yourself. The license usually allows it, as the next section explains.
5. Licensing
A font is software, and its license decides where it may go. Buying a font for a desktop typically allows installing it on a few computers to make documents and images; it usually does not allow putting the file on a web server, where every visitor downloads a copy. That needs a web license, and type foundries write their own terms.
- The SIL Open Font License (OFL), version 1.1 from 2007, is the common license for free fonts. It allows using, studying, modifying and redistributing the font, including embedding it in web pages and bundling it with software. The font may not be sold on its own, modified versions must stay under the OFL, and a font may declare Reserved Font Names that modified versions must not use. IBM Plex, Inter, Fraunces, Literata and every font in this section's demos are under the OFL, and each has its license file beside it.
- Commercial web licenses vary: priced by page views or by domain, sometimes forbidding subsetting or conversion. Some come only as a service: Adobe Fonts is part of a subscription and is served from Adobe's servers.
- System fonts are licensed to the reader, not to you. A font stack may name Helvetica and get it where it is installed; copying the file to a server is a different act, which is why this section shows such faces as pictures.
The habit: before a font goes into a project, read what its license says about web use, self-hosting and modification, and keep a copy with the files. A design built on a font the client cannot license for the web will have to change.
6. Subsetting and unicode-range
A full font covers far more characters than a typical page uses. Inter's complete release includes Cyrillic, Greek, Vietnamese and a long list of symbols; an English page needs about two hundred characters. Subsetting builds a smaller file holding only the glyphs you need, with fonttools' pyftsubset or a wrapper such as glyphhanger:
--layout-features="*" keeps all of GSUB and GPOS, so kerning and ligatures survive. Under the OFL a subset is a modified version: allowed, but the OFL FAQ says it "would not normally allow the use of" a Reserved Font Name, unless the subset stays functionally equivalent to the original or the authors agree. Inter, Fraunces, Literata and Recursive reserve no names, so this section subsets them freely. IBM Plex reserves "Plex" and Source Serif reserves "Source", which is why this section's demos use Literata rather than a Source Serif subset, and why this site serves IBM's complete, unmodified Plex files rather than smaller subsets (section 12 weighs what that costs). The extreme case is in this section's own files: the Chinese sample font for the script sampler holds the 21 characters it displays in 6,092 bytes, cut from a Noto Sans SC source of 15,054,748 bytes. Chinese, Japanese and Korean fonts hold tens of thousands of glyphs, which is why they are almost always subset or split.
Splitting with unicode-range
Cutting characters is risky when you do not know the text in advance: a name with a ŧ, a quotation in Polish or a reader's comment falls back to another font in the middle of a word. unicode-range avoids the choice. Split the font into several files, give each its own rule with the same family name and a range, and the browser downloads only the files whose ranges the page's text actually uses:
An English page fetches the first file; a page that quotes Dvořák also fetches the second; a Russian page fetches the third. This is how font services split their fonts, and self-hosted fonts can do the same. Keep each script's letters, accents and punctuation in the same file, so features that combine them still work.
7. Invisible text and flashes: FOIT and FOUT
Two acronyms name the two visible symptoms of a late font. A flash of invisible text (FOIT) is the browser drawing nothing where the text will be, so the reader sees layout, images and blank lines. A flash of unstyled text (FOUT) is the browser drawing the text in a fallback font and then redrawing it when the web font arrives: readable at once, but visibly changing.
For years browsers disagreed: Internet Explorer drew the fallback at once and swapped later, while Chrome, Firefox and Safari hid text for up to three seconds first. The CSS Fonts specification records that table and concludes that no single behavior suits every page. Neither symptom is always worse: a FOIT on body text stops reading, while a FOUT on a logo briefly shows a logo that is not the logo. Authors needed a way to say which mattered for each font, and that is font-display.
8. font-display
CSS Fonts Level 4 models the wait as a font display timeline that starts when the browser first tries to use a font face, in three periods:
- the block period: if the font is not loaded, text is drawn with an invisible fallback; if it loads, it is used;
- the swap period: if the font is not loaded, text is drawn in a visible fallback; if it loads, it is swapped in;
- the failure period: if the font has not loaded by now, it is treated as failed and the fallback stays.
The font-display descriptor sets the length of each period for one @font-face rule:
| Value | Block period | Swap period | The spec's intended use |
|---|---|---|---|
auto | The browser's own policy; the spec notes that many browsers behave like block | The default when the descriptor is left out | |
block | Short (3 s recommended) | Infinite | Only for small pieces of text that are useless in any other font |
swap | Extremely small (100 ms or less) | Infinite | Small pieces of text where the font matters but any font gets the message across, such as a logo |
fallback | Extremely small (100 ms or less) | Short (3 s recommended) | Body text and other long text, where the font is wanted but a late swap would disturb a reader already reading |
optional | The font is used only if it can be used for the text's first paint; otherwise both periods count as expired, and the browser may even cancel or deprioritize the download | Body text where the font is a nice-to-have; returning visitors, with the font cached, get it | |
The spec's advice is more careful than the common habit of putting swap on everything. For body text it recommends fallback or optional, because a reader who has started reading a paragraph is disturbed when it reflows under their eyes, and a swap after three seconds is more likely to interrupt reading than to help it. swap earns its place on short text whose look carries meaning, and block, which hides text for seconds, is meant for almost nothing. The timings are recommendations: browsers may choose others, and a browser may let its user override them, for example by setting every block period to zero.
Older descriptions give optional a 100 ms block period and no swap period, and browsers behave much that way, but the current spec defines it by the first paint. Chrome, for example, briefly holds the first paint for a preloaded optional font.
9. Preloading fonts
Fonts are discovered late by design. The browser cannot know a font is needed until it has the HTML, has fetched and parsed the CSS, found an @font-face rule, matched it to an element through font-family, and laid out text that uses it. Only then does the request start, often after every style sheet and the first images. A preload in the head announces the file while the HTML is still being parsed:
The attributes all matter:
as="font"gives the request a font's priority and lets the browser match it to the later request from the CSS.type="font/woff2"lets a browser that cannot use the format skip the download.crossoriginis required, even for a font on your own origin. The CSS Fonts specification fetches every font in CORS mode, so a font request is always a CORS request. A<link rel="preload">withoutcrossoriginmakes an ordinary no-CORS request, which does not match; the browser cannot reuse it, downloads the font a second time, and warns in the console about an unused preload. With the bare attribute (meaninganonymous) the two requests agree.
What a preload costs
A preload is a claim that this file matters more than whatever the browser would otherwise fetch first, and every claim competes with the CSS, the scripts and the hero image of the page (Image Performance makes the same point for images). So:
- Preload only what the first screen uses, usually the body face, perhaps the heading weight: one or two files. Preloading italic, bold italic and the monospace face for a code sample below the fold spends early bandwidth on text no one sees yet.
- Preload exactly the file the CSS will ask for. A different URL, a different subset or a stale version name downloads twice.
- Remove preloads that no longer match. Chrome reports a preload not used within a few seconds of the load event; designs change and preloads outlive them.
- Prefer self-hosting to preconnecting. A preload cannot name a font service's file in advance, since its URLs come from its CSS;
preconnectis the most it can do.
The HTML side, including where preloads go in the head, is in The head and link relationships.
10. Layout shift and matched fallbacks
A swap does more than change the shape of letters. The fallback and the web font have different widths, so lines break in different places, and different vertical metrics, so lines may have different heights. When the font arrives, paragraphs grow or shrink and everything below them moves. That movement counts against Cumulative Layout Shift, the Web Vital described in Image Performance, and a reader who was halfway through a sentence has to find their place again.
Images fix their shift with width and height. Fonts fix theirs by making the fallback the same size as the web font. Four @font-face descriptors adjust a font, and they can be applied to a local font, so a rule can define a new family that is Arial, resized and given the web font's vertical metrics:
| Descriptor | What it sets | What it fixes |
|---|---|---|
size-adjust | A multiplier on every glyph and every metric of the face, without changing font-size or em | Width: the same words take the same room, so lines break in the same places |
ascent-override | The height above the baseline, as a percentage of the font size | Where the baseline sits in the line, and the height of a line with line-height: normal |
descent-override | The depth below the baseline | The same, below the baseline |
line-gap-override | The extra gap a font asks for between lines | The rest of a normal line's height |
Computing the values
The values come from the fonts' tables. For Inter over Arial, the fallback demo below uses fonttools to read both:
- Both fonts have 2,048 units per em. Inter's ascender is 1,984 units, its descender 494, its line gap 0. Arial's are 1,854, 434 and 67.
- The demo's sample paragraph is 118.414 em wide in Inter (at optical size 16, the size it is shown at) and 111.615 em in Arial.
size-adjustis the ratio of widths: 118.414 ÷ 111.615 = 1.0609, or 106.09%.- The overrides are Inter's metrics, divided by that ratio, because the spec scales overrides by
size-adjusttoo: ascent (1,984 ÷ 2,048) ÷ 1.0609 = 91.31%; descent (494 ÷ 2,048) ÷ 1.0609 = 22.74%; line gap 0%. - One nudge: Inter's ascent at 16 pixels is exactly 15.5, a half-pixel edge. Browsers round line metrics to whole pixels, and 91.31% leaves the fallback a hair under 15.5, so it rounds down and each
normalline comes out a pixel short. The demo uses 91.5%, safely past the edge.
Until Inter loads, the text is drawn in the adjusted Arial; when Inter arrives, the letters change shape but each line keeps its length and its height. Liberation Sans is listed because it was designed with Arial's widths and metrics and is common on Linux, where Arial often is not. A system without either falls through to sans-serif, unadjusted. Doing this arithmetic by hand once is worth it for understanding; in practice tools such as Capsize, Fontaine and Next.js's next/font compute the values from the font files.
The demo turned up two practical limits. Inter has an optical size axis and narrows a little as it grows, so a match computed at 16 pixels drifts at 32. And with line-height: normal, line heights depend on rounding, as the nudge showed; with line-height: 1.5, every line is the same height in any font, so the match needs no nudging. The numeric line height is the robust answer.
Browser support
size-adjust works in Chrome and Edge 92, Firefox 92 and Safari 17. The three overrides have been in Chrome and Edge since version 87 and Firefox since 89, but as of October 2026 Safari has them only in its Technology Preview. In Safari, then, a matched fallback fixes widths and line breaks but not vertical metrics, which is one more reason to set line-height as a number: with a fixed line height, width is most of the match, and Safari supports width.
11. The CSS Font Loading API
Everything so far is declarative: the browser decides when to load and swap. The CSS Font Loading API lets a script watch and steer the same machinery. It has been in every major browser for years, and it has three parts:
document.fonts, aFontFaceSetholding every font face the document knows, including those from@font-facerules.document.fonts.check("1rem Inter")says whether text in that font can be drawn now without a load, anddocument.fonts.load("1rem Inter")starts the load and returns a promise.FontFace, a font face made in script.new FontFace(family, source, descriptors)takes the same descriptors as the CSS rule; itsload()returns a promise, anddocument.fonts.add()makes it usable by CSS.document.fonts.ready, a promise that resolves when the document has finished loading fonts, and theloadingdoneandloadingerrorevents.
Before font-display, scripts like this were how sites avoided invisible text. Today the descriptor does that without script, so the API's main uses are canvas text, which does not wait for fonts by itself; measuring text; fonts chosen at run time; and labs like this page's, which read document.fonts to report each face's status.
12. This site's type, as a worked example
Every page of this course is set in IBM Plex Sans, with IBM Plex Mono for code, and the setup shows most of the choices above being made. The theme style sheet declares eight static faces. They are IBM's own complete WOFF2 files, unchanged, taken from IBM's release and self-hosted in folders named for that release:
The folder name is what makes long caching safe. A file at /assets/fonts/ibm-plex-sans-1.1.0/ never changes; a new Plex release would go in a new folder, under new URLs. So the server can tell browsers to keep the files for a year and never ask about them again, with one rule in the _headers file that Cloudflare reads for static files:
max-age=31536000 is a year in seconds, and immutable tells the browser that even a reload need not check. Every other file on the site keeps the default, max-age=0, must-revalidate, which makes the browser ask the server on each use whether its copy is still current. That suits HTML and CSS, which change without changing their URLs. It is what the fonts had before, too, so every page view sent a request per font only to hear "not modified".
Measured on a cold load of Image Performance in Chrome against a local build, October 2026, the page requested seven of the eight files, 414,288 bytes in all: Plex Sans regular, italic, medium and semibold, and Plex Mono regular, medium and semibold. Bold 700 was declared but unused, so it was never downloaded. The two preloaded files started first, 13 milliseconds after navigation began, against 53 for the five found through the CSS. The course home page needed five files, 296,468 bytes. Loading either page a second time downloaded no font and sent no request for one.
Strengths
- Self-hosted, WOFF2 only. No third-party request or disclosure, and the site works offline. The build even strips the Google Fonts imports in the Vanilla Breeze framework's style sheet.
- IBM's files, unmodified. The name "Plex" stays with the files IBM made, as the license asks (section 6), and each folder carries IBM's license.
- The whole character set. Each face has about 900 characters: accented Latin for most European languages and Vietnamese, Greek and Cyrillic. A student named Dvořák, Nguyễn or Ελένη stays in Plex.
- Cached for a year. A reader pays for the fonts once; every later page, on any visit, draws in Plex at once with no request.
- Only the weights in use, so a page downloads only the faces its text asks for, and narrow preloads: only the body and heading faces.
- Fallbacks end in generic families, through
ui-sans-serifandsystem-uitosans-serif, so text is never at the mercy of one file.
Trade-offs
- A heavy first visit. Complete files are nearly three times the size of the Latin subsets the site used before: about 63 KB a face against 23. A first visit costs 300 to 415 KB of fonts, well over the budget in the next section. The site accepts that for clean licensing and full coverage, paid once a year per reader; a site whose readers mostly come once would decide the other way.
- A smaller option exists. IBM also publishes its own split files, one per script, with a style sheet that joins them through
unicode-range. They are IBM's originals too, so they keep the name, and an English page would fetch only the Latin file, about 21 KB a face. The cost is six files and rules a face instead of one. swapon long text. Every face usesswap, so on a slow first visit the reader sees system type, then Plex. The spec would suggestfallbackfor body text; the site trades a possible late swap for always ending up in its own face, which caching makes the usual case.- No matched fallback. The system UI font is unadjusted, so a swap can rewrap lines; with the body faces preloaded, it usually happens before the first paint.
- Seven requests, not one. Static weights keep each file smaller, but a page with code samples and italics makes seven requests. A variable font would be one larger file; neither is always better, and Variable fonts, page 08, does the comparison.
Complete files also reward measuring formats: IBM's Plex Sans Regular is 63,020 bytes as WOFF2 and 87,508 as WOFF, 28 percent smaller. On a tiny subset the gap nearly disappears, but WOFF2 is never much worse, so it is the right single format.
13. A font budget
A performance budget sets limits in advance and checks them on every change (an image budget works the same way). Fonts are easy to budget because they are few, and easy to break because each new weight looks harmless. A font budget has three lines:
- Families: usually one or two, and a system stack counts as zero bytes. Choosing and pairing makes the design case for few typefaces; the performance case is the same.
- Files on the first screen: the faces the first view of a typical page needs, ideally two to four, with at most two preloaded.
- Bytes: a total for the first view. On a Latin site, 100 to 150 KB of WOFF2 buys a family in several weights or one variable font, and anything over that should have a reason.
The budgets on these pages
This site breaks its own byte line on purpose: a first view of an ordinary page costs 300 to 415 KB of Plex, and later views cost nothing, for the reasons in section 12. The Fonts section adds rules of its own, enforced by its test: a lesson page loads none of the section's demo fonts itself, each demo may declare at most 750 KB, and every demo font file must stay under 150 KB, except the script fonts for the sampler, which keep whole Unicode blocks so shaping works. This page loads none: its comparisons live in the two demos, which load Fraunces (121 KB) and Inter (73 KB) only when opened.
Bytes are a means. What the reader feels is time to readable text, which depends on when the font is discovered (section 9) and what is drawn meanwhile (section 8), and stability, which depends on the fallback (section 10). Budget the outcomes too: no invisible text on the first screen, and no layout shift from a font swap.
14. Summary
| Concept | Key points |
|---|---|
| The cost | While a font loads, the browser hides the text, shows a fallback and swaps, or keeps the fallback; each costs something. |
@font-face | Describes a face: family name, src, matching descriptors (weight, style, width, unicode-range) and behavior (font-display, metric overrides). Files download only when text uses them. In CSS2 (1998), dropped from CSS 2.1, back from 2008. |
| Formats | A font is tables: cmap, glyf or CFF, GSUB, GPOS, hinting, metrics. EOT (Internet Explorer) and SVG fonts (early iPhones) are history; WOFF (2012) and WOFF2 (2018) wrap the same tables compressed. Serve WOFF2 alone. |
| Hosting and privacy | Services add origins and a third-party request per visit; caches are per site, so nothing is shared. A Munich court (3 O 17493/20, 2022) found sending the IP address to Google unjustified when self-hosting was possible. |
| Licensing | A desktop license is not a web license. The OFL allows self-hosting and modification; a subset is a modified version, which should not keep a Reserved Font Name. Commercial terms vary; system fonts belong to the reader's system. |
| Subsetting | Keep only the glyphs you need with pyftsubset, keeping layout features. Split by script with unicode-range so pages fetch only what their text uses. |
FOIT, FOUT, font-display | Block, swap and failure periods. block: about 3 s invisible, then swap; swap: fallback at once, swap any time; fallback: swap within about 3 s or never; optional: the font only if ready for first paint. The spec suggests fallback or optional for body text. |
| Preload | Fonts are found late; preload the one or two the first screen needs, with as="font", type and crossorigin, which fonts need even from the same origin because they are fetched in CORS mode. |
| Layout shift | Match the fallback to the font: size-adjust for widths, ascent-override, descent-override and line-gap-override for vertical metrics, computed from the fonts' tables. Safari has size-adjust but not yet the overrides; a numeric line-height makes width most of the match. |
| Font Loading API | document.fonts, FontFace, fonts.ready: for canvas text, measurement and run-time fonts. |
| Caching | Put the release in the font URL, then serve the files with Cache-Control: public, max-age=31536000, immutable: readers pay once, and later pages send no request. |
| A budget | Few families, two to four files on the first screen, at most two preloads, a byte limit checked automatically, and outcomes: no invisible text, no shift. |
