# fonts.md — which fonts a Figma agent can actually use

**Version:** 1.0.1 · **Catalog ID:** `reference.fonts` · **Updated:** 2026-08-18
**Applies to:** every workflow that writes text into Figma — `5-figma-canvas`, `6-design-system`, `8-figma-critique`.

All numbers below were measured against the live API on 2026-07-28, not inferred.

---

## 1. Being listed is not the same as being usable

This is the rule that matters, and it is counter-intuitive:

**`listAvailableFontsAsync()` lists fonts that `use_figma` cannot actually render.**
`loadFontAsync()` resolves for them. No error is thrown. `node.fontName` reports the font you
asked for. And the glyphs still come out **zero-width**.

Measured — same word, same size, same script:

| Font | Text node width | Height |
|---|---|---|
| Inter / Semi Bold | 91 | 58 |
| Inter / Regular | 88 | 58 |
| Roboto / SemiBold | 83 | 56 |
| Plus Jakarta Sans / SemiBold | 93 | 60 |
| **SF Pro / Semibold** | **0** | 15 |
| **SF Pro / Regular** | **0** | 15 |
| **SF Compact / Regular** | **0** | — |
| **SF Pro Rounded / Regular** | **0** | — |
| **SF Compact Rounded / Regular** | **0** | — |

The whole SF family is a phantom entry: present in the font list because the *desktop* app can
supply it locally, unusable from the cloud where `use_figma` runs. Apple's licence is the likely
reason Figma cannot serve the files server-side.

**Therefore: the only trustworthy check is to measure.** After setting `characters`, read
`node.width`. Zero means the font did not render, whatever the API told you:

```js
t.fontName = { family: FAMILY, style: STYLE };
t.fontSize = 48;
t.textAutoResize = 'WIDTH_AND_HEIGHT';
t.characters = 'test';
if (t.width === 0) { /* unusable — substitute, and say so in chat */ }
```

Do this once per unfamiliar family, on a throwaway node, before building anything real on it.

## 2. Local machine fonts are unreachable — full stop

`use_figma` runs in Figma's cloud, not in the designer's desktop app. `listAvailableFontsAsync()`
returns **1,938 families / 8,927 entries** — the Google Fonts catalogue plus the phantom SF entries
above. Of ~829 fonts installed on the designer's Mac (Aeonik, ALS Hauss, Alliance, Bolkit),
**zero** were reachable. Even `Helvetica`, a macOS system font, is absent.

No local setting changes this. Do not send a designer to reinstall a font, restart Figma, or
"enable the font helper" — none of it touches the cloud execution context. The local `figma_agent`
process serves the desktop renderer only.

## 3. Style strings differ per family

Necessary, but — per §1 — not sufficient. A wrong style string throws
`Cannot write to node with unloaded font`:

Three spellings of the same weight, across four families:

| Family | Semibold is spelled |
|---|---|
| Inter | `Semi Bold` — two words |
| Roboto | `SemiBold` — one word, capital B |
| Plus Jakarta Sans | `SemiBold` — one word, capital B |
| SF Pro | `Semibold` — one word, lowercase b *(academic: it does not render anyway)* |

Never transcribe a style string from one family to another — Inter's `Semi Bold` throws on Plus
Jakarta Sans, and vice versa. Don't hardcode a guess either; **pick from the returned list**:

```js
const all = await figma.listAvailableFontsAsync();
const styles = all.filter(f => f.fontName.family === FAMILY).map(f => f.fontName.style);
const pick = styles.find(s => /^semi\s?bold$/i.test(s)) || styles.find(s => s === 'Regular');
```

## 4. Practical defaults

**Use Inter.** It renders, it is preloaded in most contexts, and its metrics are close to SF Pro —
which makes it the right stand-in for iOS and macOS work where SF is the real target. Roboto is
the equivalent for Android.

**Never SF Pro, SF Compact or their Rounded variants** in agent-built Figma work. Not because of a
naming quirk — because they measurably do not render. If the design must ship in SF, build in Inter
and have the designer swap the family in the **desktop app**, which can see the real font.

## 5. When the project needs a font Figma cannot serve

In order of preference:

1. **Publish it to the Figma org.** Shared/organisation fonts are uploaded once and become
   available server-side — the only route that makes a custom font reachable by the agent. Plan-gated
   (Organization / Enterprise); verify the team's plan before promising it. Humbleteam's team is on
   `pro`, so this is not available there today.
2. **Substitute from the available families** and record the substitution in the project's
   `context.md` — which font you used, and which one it stands in for. Never swap silently.
3. **Build with a stand-in, designer swaps in the desktop app.** One consistent placeholder family;
   the designer selects the frame in the desktop app and changes the family once. Prefer this when
   the layout matters more than the typeface during the build.
4. **Do the type-critical pass by hand** — when the exact typeface drives the decisions being made.

Whatever the choice, say it in chat. A designer who does not know a substitution happened will read
the mockup as a proposal for that typeface.

## 6. Loading

Use the runtime's approved font-loading adapter to emit one `loadFontAsync` per family×style
declared in the project's `ds-tokens.html`; do not depend on an unshipped source-tree helper.
The adapter emits whatever the DS declares, so if the DS names an unrenderable font the generated
block will "succeed" and produce invisible text: verify per §1 first.

`await` every load before creating or mutating text, including `appendChild` / `setBoundVariable`
on nodes that already carry text.
