· Performance · 7 min read
How to Subset a Web Font, and What unicode-range Really Does
Remove the characters your site never uses to make font files smaller, then use unicode-range so browsers only download the subsets a page needs.
Many font files contain far more characters than a website uses: several alphabets, hundreds of symbols, accented letters for dozens of languages. Every one of them is downloaded by every visitor.
Two tools deal with this, and they are often confused:
- Subsetting removes characters from the font file, so the file gets smaller.
unicode-rangetells the browser which characters a file covers, so it can skip files the page does not need. It does not make any file smaller.
Used together, they let a page download only the characters it actually shows. This guide covers both.
What unicode-range does, and what it does not
unicode-range is an @font-face descriptor that lists the characters a face should be used for. MDN’s unicode-range reference describes the download rule: if the page uses none of the characters in the range, the font is not downloaded; if it uses at least one, the whole file is downloaded.
That last part matters. Adding unicode-range: U+0000-00FF to a 400 KB font that covers twenty languages does not shrink the download. A page with one Latin letter still fetches all 400 KB. To save bytes, the file itself has to be smaller, which is what subsetting does.
Step 1: Decide which characters you need
Start with what the font contains. Open it in the font viewer: it shows how many characters the font includes and lists every one of them. Then decide what your content needs. Unicode groups characters into blocks, which makes good starting points:
| Range | Unicode block | Covers, for example |
|---|---|---|
| U+0000–007F | Basic Latin | English letters, digits, ASCII punctuation |
| U+0080–00FF | Latin-1 Supplement | é ñ ü ß ç £ © × and other Western European characters |
| U+0100–017F | Latin Extended-A | ł ś ż č ř ő and other Central European letters |
| U+2000–206F | General Punctuation | curly quotes ‘ ’ “ ”, dashes – —, ellipsis … |
| U+20A0–20CF | Currency Symbols | € ₹ and other currency signs |
The Unicode code charts list every block. A few things are easy to miss:
- Punctuation and symbols your editor inserts automatically, such as curly quotes, dashes,
™, and€. - Names and user content. Product reviews, comments, and customer names can contain letters from any language. If visitors can type it, be generous, or keep a second subset for the rest (see step 3).
- Every page type. The checkout page and the blog may use different characters.
Before you subset, paste real content into “Test your text” in the font viewer. Characters the font is missing are listed under the preview; after subsetting, the same check tells you whether you removed something you need.
Step 2: Make the subset
With fontTools
fontTools is a widely used open-source Python library for working with fonts. Its subset command takes a font, a list of characters, and an output format:
pip install fonttools brotli
fonttools subset BrandSans-Regular.ttf \
--unicodes="U+0000-00FF,U+2000-206F,U+20AC,U+2122" \
--layout-features='*' \
--flavor=woff2 \
--output-file=brand-sans-regular-latin.woff2What the options do, according to the fontTools documentation:
--unicodeslists the characters to keep, as code points or ranges. Use--text="…"or--text-file=path.txtto keep exactly the characters in a piece of text instead.--layout-features='*'keeps all OpenType features. Without it, fontTools keeps a default set (including kerning, standard ligatures, and contextual alternates) and drops the rest, such as small caps or stylistic sets. If your CSS uses any of those, keep them; see OpenType features in CSS.--flavor=woff2writes a WOFF2 file directly. It needs thebrotliPython package, which is why it is installed above.--output-filesets the name of the new file.
Repeat the command for each weight and style you use. “Brand Sans” is a placeholder; use your own file names.
In your browser
FontPivot’s font subsetter does the same job without installing anything. It runs HarfBuzz, an open-source font engine, in your browser, so the font is not uploaded. It starts with the same Latin character set Google Fonts serves; you can add other sets such as Latin Extended-A, type extra characters, or enter Unicode ranges, and the real file size updates as you go. OpenType features are kept unless you untick them, every name record (including the license text) stays in the file, you can add a whole family at once, and the web font kit’s CSS includes the matching unicode-range. A “Split like Google Fonts” option makes one file per script instead, as described in step 3.
Through a framework or service
Font services and some frameworks subset for you. Google Fonts, for example, serves each family as several files split by script, and Next.js’s next/font lets you choose which of those subsets to preload. If you self-host from one of those sources, check whether the files you download are already subset; often they are not. How to self-host Google Fonts covers that case.
Step 3: Describe each subset with unicode-range
One subset is enough for many sites. If your content is mostly in one language but occasionally needs others, split the font into a main file and an extra one, and let the browser decide:
/* Main file: almost every page needs it */
@font-face {
font-family: 'Brand Sans';
src: url('/fonts/brand-sans-regular-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+2000-206F, U+20AC, U+2122;
}
/* Extra file: only downloaded when a page uses one of these characters */
@font-face {
font-family: 'Brand Sans';
src: url('/fonts/brand-sans-regular-latin-ext.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0100-017F;
}Make the second file the same way, with --unicodes="U+0100-017F". Both rules share one font-family, so your CSS uses the font as usual. For each character, the browser picks the face whose range includes it.
Keep each unicode-range in step with the characters really in the file. If the range promises a character the file does not have, the browser downloads the file and then still draws that character with a fallback font.
Things that can go wrong
Kerning and ligatures across files. Spacing pairs and ligatures are applied within one font file. If two neighboring letters come from different subset files, the kerning between them is lost. Keep characters that commonly appear together, such as a language’s whole alphabet, in the same subset.
Dropped OpenType features. A subsetting tool that keeps only default features removes small caps, old-style figures, or stylistic sets. Check the result in the font viewer’s OpenType features panel, which lists the features a file contains, and compare it with the original.
Preloading the wrong file. A preload ignores unicode-range, as web.dev’s Best practices for fonts points out. Preload only the main subset, never every file. How to preload web fonts explains why.
Characters that appear later. Search results, comments, or translated pages can show characters you removed. Those fall back to another font, which often looks like a typo. A second subset with unicode-range is the safety net.
The license. Subsetting changes the font file. Some licenses set rules for modified fonts, for example reserved font names in the SIL Open Font License. Read your font’s license, and ask the foundry if it is not clear whether subsetting is allowed.
Keep the original file. You will need it whenever you want to add characters back.
Check the result
- Compare file sizes. The subset should be noticeably smaller. If it is not, the original may already have been subset.
- Open the subset in the font viewer. Check the character count, paste your real content into “Test your text”, and look for missing characters under the preview.
- Check what loads. In your browser’s developer tools, filter the Network panel by “Font”. A page in your main language should load only the main subset. A page with extended characters should load both.
If you are starting from a desktop TTF or OTF and do not need subsetting, the TTF to WOFF2 and OTF to WOFF2 converters produce a web-ready file in one step, and the font converter handles the other formats.
Summary
unicode-rangedecides when a file is downloaded. It never makes a file smaller.- Subsetting makes the file smaller by removing characters.
- Keep the punctuation, symbols, and letters your content really uses, and keep the OpenType features your CSS relies on.
- Split rarely used characters into a second file with its own
unicode-range. - Check the subset with real content before you ship it.
Further reading
- MDN:
unicode-range - web.dev: Reduce web font size
- fontTools:
subsetdocumentation - Unicode: Code charts