Shrink Font Awesome webfonts to only the icons you actually use — safely, automatically, and fast.
- FA6 & FA5 compatible (solid, regular, light, thin, duotone, brands, sharp variants, FA5 aliases)
- Works with minified CSS and chained selectors (e.g.
.fa-duotone.fa-house:after) - Handles duotone’s dual codepoints (
"\f015\f015") - Ignores non-icons (sizes like
fa-2x, utilities likefa-fw, animations, etc.) - Multi-pack subsetting (one subset per family)
- Dry-run report with per-file & per-icon breakdown (+ JSON)
- Sensible defaults; strict checks available
Not affiliated with Font Awesome / Fonticons, Inc.
Bring your own licensed FA CSS/fonts and comply with your Font Awesome license (Free or Pro).
This tool never ships FA assets; it subsets the files you provide.
Browsers download the entire font file once any glyph is needed. If your site uses 100 icons but your FA font ships thousands, you pay for everything.
This tool builds tiny, per-style fonts that contain only the glyphs you actually use. Real numbers from production:
- Fonts: 803 KB → 14.5 KB (98.2% reduction)
- Total FA payload (CSS + fonts): ~1.29 MB → ~0.50 MB (~61% reduction)
Faster first paint, happier Lighthouse, less bandwidth — especially for mobile users.
- Python 3.9+
fonttools(forpyftsubset)
pip install fonttools- Your own Font Awesome CSS & font files (Free or Pro).
Do not commit these assets to the repo if they’re licensed.
- Clone this repository or add the script to your project (
bin/fa-subset.py). - Run a dry run to preview what will be sub‑set:
python3 bin/fa-subset.py \
--src ./src ./templates ./module \
--ext .phtml .php .twig .html \
--pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf \
--pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf \
--pack brands:css=./public/css/brands.min.css,font=./public/webfonts/fa-brands-400.ttf \
--out ./public/css/fa-custom \
--default-pack solid \
--strict-styles \
--dry-run --report-json ./public/css/fa-custom/report.json- Build the subsets (remove
--dry-run):
python3 bin/fa-subset.py \
--src ./src ./templates ./module \
--ext .phtml .php .twig .html \
--pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf \
--pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf \
--pack brands:css=./public/css/brands.min.css,font=./public/webfonts/fa-brands-400.ttf \
--out ./public/css/fa-custom \
--default-pack solid \
--strict-styles- Include the emitted CSS alongside your app:
<link rel="stylesheet" href="/css/fa-custom/solid/fontawesome.solid.subset.min.css">
<link rel="stylesheet" href="/css/fa-custom/duotone/fontawesome.duotone.subset.min.css">
<!-- add brands/sharp subsets if you used them -->Tip: The CSS you reference for each
--packmust include icon rules. For FA6, that’s typicallyall(.min).cssor the per‑style files likeduotone(.min).css,solid(.min).css, etc. If a file lacks icon rules, the script will warn you.
usage: fa-subset.py --src DIR [DIR ...] --pack name:css=PATH,font=PATH [--pack ...] --out DIR [options]
--src DIR [DIR…]— Directories to scan (recursive).--pack name:css=PATH,font=PATH— Repeat per FA pack (e.g.,solid,duotone,brands,sharp-*).--out DIR— Output directory (subfolders per pack).
--ext .twig .phtml .php .html— Restrict scanning to these extensions (default: all non‑binary).--dry-run— Don’t write fonts/CSS; print a human report (plus optional JSON).--report-json PATH— Write a machine‑readable report (icons per pack, per‑file mapping, conflicts).--default-pack {solid,regular,light,thin,duotone,brands,sharp-*,none}— Style to assume if a line has no style class (default:solid).--strict-styles— Error out if a line mixes multiple styles (e.g.,fa-duotone+fa-solid).
0ok1pyftsubsetmissing2nofa-*usage found in sources4style conflicts with--strict-styles
For each --pack NAME you provided:
/out/
NAME/
fontawesome.NAME.subset.woff2
fontawesome.NAME.subset.css
- The subset WOFF2 contains only glyphs you actually used for that pack.
- The subset CSS keeps required base rules/variables and the icon rules for used icons.
- Scan source for
fa-*tokens and detect the style on each line (fa-solid,fa-duotone,fa-brands, sharp variants, or FA5 aliases likefas,fad). - Parse your FA CSS to map icon → codepoints. Works on minified CSS and chained selectors (
.fa-duotone.fa-house:after). Handles duotone’s two layers. - Subset each pack’s font via
pyftsubsetto just those codepoints; emit WOFF2. - Trim CSS to keep essential base + used icon rules.
The CSS you pass per pack needs the icon rules (e.g., .fa-house:before { content:"\f015" }).
- Usually works:
all(.min).cssfor all styles, or per‑style filessolid(.min).css,duotone(.min).css,brands(.min).css, etc. - May not work: a core
fontawesome(.min).cssthat only defines variables/base without per‑icon mappings (you’ll see a warning and zero mappings).
You can point multiple packs at the same combined CSS (e.g., all.min.css), or at their own per‑style CSS — both are fine.
When --report-json PATH is provided, the file contains:
{
"packs": {
"solid": {
"icons_found": ["user", "house", "..."],
"icons_missing_in_css": [],
"codepoints": ["U+F007", "U+F015"],
"icons_to_files": {"user": ["templates/nav.phtml"], "house": ["..."]}
},
"duotone": { "...": "..." }
},
"conflicts": [
{"file": "templates/nav.phtml", "line": 42, "icon": "screwdriver-wrench", "styles": ["duotone", "solid"]}
],
"missing_pack_refs": [
{"file": "...", "line": 1, "icon": "...", "wanted_pack": "sharp-duotone"}
],
"file_to_icons": {
"templates/nav.phtml": [ {"pack": "solid", "icon": "user"} ]
}
}Use it in CI to fail builds on conflicts or unexpected style usage.
- Cache aggressively: serve subset CSS/WOFF2 with long
Cache-Controland Brotli (or gzip) compression. - Cache busting: include a hash in filenames (or append
?v=hash). - One style per icon: don’t mix
fa-duotoneandfa-solidon the same element. - CORS: fonts are cross‑origin fetches — ensure your CDN/site sends permissive CORS headers if needed.
“No icon rules found in CSS”
Use all(.min).css or the per‑style CSS with icon selectors. The tool warns if the file you passed is only a variables/base sheet.
Duotone icons not rendering
Verify the duotone pack is provided (--pack duotone:css=...,font=fa-duotone-900.ttf) and your markup uses fa-duotone (not mixed with fa-solid).
Mixed styles warning
Fix markup to one style per icon. Or run without --strict-styles if you want a soft warning.
Subset WOFF2 is empty/tiny
No icons matched that pack. Check your --ext filters and style classes.
Fonts load but icons show tofu (squares)
Check that the subset CSS is included after your theme overrides, and that the @font-face src URLs are reachable (Network tab). Verify CORS.
Fonts
- Original: Solid 345 KB (
fa-solid-900.woff2), Duotone 458 KB (fa-duotone-900.woff2) — Total 803 KB - Optimized: Solid 2.5 KB (
fontawesome.solid.subset.woff2), Duotone 12 KB (fontawesome.duotone.subset.woff2) — Total 14.5 KB
Reductions
- Solid: 345 KB → 2.5 KB (−99.3%)
- Duotone: 458 KB → 12 KB (−97.4%)
- Overall fonts: 803 KB → 14.5 KB (−98.2%)
Icons included
- Solid pack: 20 icons
- Duotone pack: 99 icons
- Brands pack: 0 icons (not used)
CSS size remains roughly the same in conservative mode (keeps base rules/variables); the huge win is the fonts. You can enable an aggressive trimming mode if you want to shrink CSS further.
By default, the subset CSS keeps all base rules/variables to be maximally safe. If you need smaller CSS, you can modify the writer to keep only:
@font-facefor the subset font- Base style classes you actually use (e.g.,
.fa-solid,.fa-duotone) - Modifiers you actually use (
fa-2x,fa-fw, animations, etc.) - The icon rules for used icons
This can reduce a ~490 KB minified sheet to tens of KB, but increases maintenance risk. Consider making it opt‑in behind a flag.
- No Font Awesome assets are shipped. You must supply your own FA CSS/fonts and comply with your Font Awesome licence. Not affiliated with Fonticons, Inc.
- Licensed under BSD-2-Clause. © 2025 Sheridan Internet Limited.
SeeLICENSEfor the full text.
- Sam Sheridan — original author & maintainer
- Sheridan Internet
Issues and PRs welcome! Please:
- Include reproduction steps and a minimal sample if reporting CSS parsing issues.
- Add/adjust fixtures in
examples/and tests intests/for parser changes. - Keep dependencies minimal (only
fonttools).
fa-subset/
├─ bin/fa-subset.py # the main script (CLI)
├─ bin/fa-minify.sh # optional CSS minifier
├─ examples/
│ ├─ sample.html # tiny demo page (no FA assets)
│ └─ sample.css # synthetic test CSS you wrote yourself
├─ tests/
│ └─ test_parser.py # unit tests against sample.css/html
├─ .gitignore
├─ LICENSE # BSD-2-Clause
├─ README.md
└─ pyproject.toml # optional: package as a CLI
.gitignore (protect yourself):
*.woff
*.woff2
*.ttf
*.otf
*.eot
public/webfonts/
public/css/*fontawesome*.css
public/css/*duotone*.css
public/css/*solid*.css
Add handy scripts to your composer.json so anyone on the team can analyze/optimize with a single command.
Prereqs:
python3andpyftsubset(fromfonttools) must be onPATH.
{
"scripts": {
"fa-analyze": "python3 bin/fa-subset.py --src ./src ./templates --ext .phtml .php .html .twig --pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf --pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf --out ./public/css/fa-custom --default-pack solid --dry-run --report-json ./public/css/fa-custom/fa-report.json",
"fa-optimize": [
"python3 bin/fa-subset.py --src ./src ./templates --ext .phtml .php .html .twig --pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf --pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf --out ./public/css/fa-custom --default-pack solid",
"@fa-minify"
],
"fa-minify": "sh bin/fa-minify.sh",
"fa-check": "python3 bin/fa-subset.py --src ./src ./templates --ext .phtml .php .html .twig --pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf --pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf --out ./public/css/fa-custom --default-pack solid --strict-styles --dry-run --report-json ./public/css/fa-custom/fa-report.json"
}
}composer fa-analyze # dry-run report + JSON
composer fa-optimize # write subset fonts + CSS, then minify CSS
composer fa-check # strict CI-style analysis (fails on conflicts)If you don’t use Brands (or other packs), omit those
--packlines to skip generating unused subsets.
.github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -U pip fonttools pytest
- run: pytest -qThis repo includes a small helper: bin/fa-minify.sh.
It cleans the subset CSS and rewrites @font-face URLs to the local subset .woff2.
What it does
- Flattens/minifies whitespace (safe “micro-minify”).
- Rewrites
url(../webfonts/...)(quoted or unquoted) →url(fontawesome.<pack>.subset.woff2). - Removes legacy
.ttffallbacks (and theirformat('truetype')) from@font-face. - Auto-detects Linux/FreeBSD/macOS
sedfeatures (prefersgsedif available).
When to run
Run it after fa-subset.py has generated:
public/css/fa-custom/<pack>/fontawesome.<pack>.subset.css
public/css/fa-custom/<pack>/fontawesome.<pack>.subset.woff2
Composer scripts
{
"scripts": {
"fa-optimize": [
"python3 bin/fa-subset.py --src ./src ./templates --ext .phtml .php .html .twig --pack solid:css=./public/css/fontawesome.min.css,font=./public/webfonts/fa-solid-900.ttf --pack duotone:css=./public/css/duotone.min.css,font=./public/webfonts/fa-duotone-900.ttf --out ./public/css/fa-custom --default-pack solid",
"@fa-minify"
],
"fa-minify": "sh bin/fa-minify.sh"
}
}chmod +x bin/fa-minify.sh
sh bin/fa-minify.shFor each pack (e.g., solid, duotone) you’ll get:
public/css/fa-custom/<pack>/fontawesome.<pack>.subset.min.css
Reference the .min.css in your HTML (it points at the subset .woff2).
Tip: keep the original FA CSS/fonts out of production if you don’t need them; the subset CSS is self-contained.
- Icons show blank/squares → ensure you’re including the new fontawesome..subset.min.css (not the original sheet), and the .woff2 is being served (check DevTools → Network).
- Browser still requests original FA fonts → your CSS wasn’t rewritten; re-run fa-minify and verify the url(...) now points to fontawesome..subset.woff2.
- CORS errors on fonts → serve the .woff2 from the same origin as the page or add the correct Access-Control-Allow-Origin header.
- The awesome
fonttoolsproject for robust font subsetting. - Font Awesome for their icon ecosystem (again: this project isn't affiliated).
Happy subsetting! 🎯
Made by Sheridan Internet Limited