---
name: artifact-font-embedding
description: This skill should be used when the user asks to "show me font pairing options", "preview typography choices", "compare fonts", "mock up different fonts", or asks to see design/typography options rendered with real fonts inside a Claude Artifact. Also applies whenever a Worker/Artifact needs a Google Font and the CSP blocks external font requests. Covers sourcing real font files via Fontsource and embedding them as base64 @font-face data URIs so an Artifact renders the actual typeface rather than silently falling back to a system font.
version: 0.1.0
---
# Embedding real fonts in Artifacts
Claude Artifacts run under a strict CSP that blocks requests to any external
host — including a `` to Google Fonts or any other font CDN. Linking
one anyway doesn't error visibly; it silently falls back to the next font in
the stack, and the artifact looks like it's using the right typeface when it
isn't. The only reliable way to show a *real* typeface inside an Artifact is
to inline the font file itself as a base64 `@font-face` data URI.
This matters most for font-pairing comparisons ("show me 5 options for this
site's typography") and any other design mockup where the actual letterforms
are the point — a system-font fallback defeats the purpose of the preview.
## Core workflow
1. **Source the real font files via [Fontsource](https://fontsource.org/)**,
not by hand-downloading from Google Fonts. Fontsource packages the entire
Google Fonts catalog (plus some others) as npm packages built for
self-hosting — this is the fastest way to get a real `.woff2` file for any
named typeface.
```bash
mkdir -p /tmp/font-scratch && cd /tmp/font-scratch
npm init -y >/dev/null 2>&1
npm install @fontsource-variable/fraunces @fontsource-variable/inter
# Static (non-variable) families use the plain package instead:
npm install @fontsource/libre-caslon-display
```
Prefer the `@fontsource-variable/*` package when one exists (most popular
families have one) — a single file covers the whole weight range instead
of needing one static file per weight.
2. **Locate the needed file(s)** under `node_modules/@fontsource*//files/`.
File names encode subset, axis, and style, e.g.
`fraunces-latin-wght-normal.woff2` (variable, weight axis, upright) or
`fraunces-latin-wght-italic.woff2` (italic). For a Latin-script preview,
the `-latin-` (not `-latin-ext-`) file is almost always the right one —
see `references/fontsource-cheatsheet.md` for the full naming pattern and
how to confirm a family's actual variable axes before writing `@font-face`.
3. **Write a Node build script that reads and base64-encodes each file, then
injects it into an HTML template — do not inline base64 by hand in a Write
call.** A single variable font file is tens of KB; base64-encoding inflates
that further, and typing or reasoning over that text directly burns
context for no benefit. Let a script do it and write the assembled output
straight to disk. Template in `scripts/build-font-preview.js` — copy it,
adjust the `FONTS` list and the HTML/CSS content, run `node build.js`.
4. **Validate the generated file before publishing** — the template-literal
nesting (CSS inside a JS template string, itself containing `${...}`
interpolations) is easy to get subtly wrong:
```bash
grep -c '${' output.html # should print 0 — any hit means an interpolation leaked through unescaped
python3 -c "import re; html=open('output.html').read(); \
[print(t, 'MISMATCH') for t in ['div','section','span','p'] \
if len(re.findall(f'<{t}(?:\\\\s[^>]*)?>', html)) != len(re.findall(f'{t}>', html))]"
```
5. **Publish with the Artifact tool.** Pick a favicon emoji and keep it
stable across redeploys of the same comparison (redeploy to the same file
path to update the same URL rather than minting a new one for every
revision the user asks for).
## Design content, not just fonts
When building a font-pairing comparison, follow the `artifact-design` skill's
"build with real content" principle — reuse the actual site's real copy,
real album/page names, real color tokens, and real type-scale values (don't
invent a generic lorem-ipsum sample). Show the *current* setup as a baseline
section for direct comparison, not just the candidates in isolation. If the
site already has an established design system (colors, spacing tokens),
inherit it into the comparison page rather than picking new ones — the
comparison should isolate the font variable, not restyle everything at once.
## Performance context worth relaying to the user
If the underlying request is really "what fonts should I use on my
site" rather than just "show me a preview," mention that self-hosting via
Fontsource (or downloading the same files into the project's own assets) is
more performant than linking Google Fonts' CDN directly in production: the
Google Fonts CDN requires two extra cross-origin connections (the CSS host
and the font-file host), even with `preconnect` hints, and sends visitor
requests to Google. **[Bunny Fonts](https://fonts.bunny.net/)** is a
same-syntax, privacy-respecting drop-in replacement for the Google Fonts
`` tag if the user doesn't want to vendor font files into their repo
but still wants better performance/privacy than Google's CDN directly.
## Additional resources
- **`references/fontsource-cheatsheet.md`** — package naming patterns,
variable vs. static file naming, how to check a family's real weight/axis
range, and common pitfalls (missing `format("woff2-variations")`, wrong
subset file, italic-only families).
- **`scripts/build-font-preview.js`** — a copy-paste build-script skeleton:
reads font files, base64-encodes them, injects into an HTML template, and
writes the final artifact HTML to disk.