---
title: "Exporting tokens"
description: "One-click export of variables and styles as W3C DTCG JSON, and what the export contains."
url: https://docs.systhema.app/next/design/figma/export
version: unreleased (main)
docs_index: https://docs.systhema.app/next/llms.txt
---

The plugin exports every local variable collection and style in a Figma file as W3C DTCG JSON, bundled into one ZIP with a `manifest.json`.

## Single-click export

Click **Export** in the plugin UI. The plugin runs five stages, posting progress messages back to the UI iframe:

1. **Export variables.** Walks every local variable collection and every mode within each collection. Each variable's value (or alias) is written as a DTCG `$value` block, with full Figma metadata in `$extensions` (variable id, scopes, alias data, hidden-from-publishing, type override for `STRING`). One JSON file per `(collection, mode)` pair, named `<collection>.<mode>.tokens.json`. Collection names with leading underscores (`_config`, `_calc`) drop the underscore in the filename but preserve it under `manifest.collections.{key}.originalName`.
2. **Export text styles.** Local text styles → `text.styles.tokens.json`. Each style becomes a DTCG composite token (`fontFamily`, `fontWeight`, `fontSize`, `lineHeight`, `letterSpacing`).
3. **Export effect styles.** Local effect styles → `effect.styles.tokens.json`. Drop shadows, inner shadows, and layer blurs become DTCG `shadow` tokens.
4. **Export grid styles.** Local layout grid styles → `grid.styles.tokens.json`.
5. **Generate manifest + ZIP.** Builds `manifest.json` with `name`, `generator: { name, version, exportedAt }`, and `collections` / `styles` maps. Bundles every token file plus the manifest into a single ZIP, then posts the ZIP back to the UI as a download.

The user clicks "Save" on the ZIP, unzips into `<project>/src/tokens/` (overwriting the previous export), and runs:

```bash
pnpm systhema-core sync
```

…to regenerate the project's CSS variables, types, and safelist. (Use `systhema sync` from the global CLI to also refresh agent-facing TOON references.)

The export is **deterministic on Figma's variable order**: variables are walked via `collection.variableIds` (Figma's UI ordering), and within each token file leaves are sorted before groups via `sortLeavesFirst()` to match Figma's native export shape. Re-exporting an unchanged design produces an identical ZIP — only the `manifest.exportedAt` timestamp differs.

## What the export contains

A typical export produces these files inside the ZIP:

**Variable collections (one file per collection-mode pair):**

- `colorPrimitives.value.tokens.json` — raw colour scales (`primitives.gray.50` … `primitives.gray.950`, brand scales).
- `colorSystem.default.tokens.json`, `colorSystem.dark.tokens.json`, plus any custom themes — semantic colour mapping per theme.
- `responsiveSizing.sm.tokens.json`, `responsiveSizing.md.tokens.json`, `responsiveSizing.lg.tokens.json` — typography sizes, container widths, grid configs, paddings per breakpoint.
- `font.value.tokens.json` — font families, weights, fallback chains.
- `tailwindcss.value.tokens.json` — if the Figma file ships Tailwind extensions (rare; usually project-side).
- `config._config.tokens.json` and `calc._calc.tokens.json` — plugin-internal helper values.
- Any motion collection the file carries (e.g. `easings.value.tokens.json`, `transition.value.tokens.json`) — Figma's `Easing` and `Timing` variables, exported as `easing` and `duration` tokens (see below).

**Style collections (one file per style type):**

- `text.styles.tokens.json` — `.text-h1` … `.text-h6`, `.text-body`, `.text-lead`, `.text-small`, `.text-label` definitions.
- `effect.styles.tokens.json` — named shadow / blur effects.
- `grid.styles.tokens.json` — named grid configurations.

**`manifest.json`** — the index:

```json
{
  "name": "Systhema Figma Tokens",
  "generator": {
    "name": "@systhemaui/figma",
    "version": "1.5.0",
    "exportedAt": "2026-04-25T11:05:24+02:00"
  },
  "collections": {
    "colorSystem": {
      "modes": {
        "default": ["colorSystem.default.tokens.json"],
        "dark": ["colorSystem.dark.tokens.json"]
      }
    },
    "config": {
      "modes": { "_config": ["config._config.tokens.json"] },
      "originalName": "_config"
    }
  },
  "styles": {
    "text": ["text.styles.tokens.json"],
    "effect": ["effect.styles.tokens.json"],
    "grid": ["grid.styles.tokens.json"]
  }
}
```

The `collections.{key}.originalName` field preserves Figma's collection name when it's been renamed for the filename (e.g. underscore prefix stripped).

### Font families carry a CSS fallback stack

Figma stores a font family as a single bare name — `Geist`, `Switzer` — because that's what it needs to resolve a real font face. CSS needs more than that: webfonts load with `font-display: swap`, so a bare `font-family: Switzer` paints once in the browser's built-in **serif** before the face arrives. That's the Times New Roman flash on a cold load.

So the export appends a system-sans fallback stack to every font-family value:

```jsonc
// font.value.tokens.json — what Figma stores: "Switzer"
"fontFamily": {
  "$type": "string",
  "$value": "Switzer, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
}
```

This applies to font-family **variables** (anything scoped `FONT_FAMILY`, or named `…/fontFamily`) and to the `fontFamily` a **text style** ships in its `$value`. Values that already contain a comma are treated as an author-provided stack and left exactly as written, as are DTCG references like `{font.default.heading.fontFamily}` and bare generic families like `monospace`. A family name that wouldn't be valid unquoted CSS (`Font 2`, `Röck+Roll`) is quoted.

**On import the stack is stripped back to the first family** before the value reaches Figma, so importing `Geist, system-ui, …` behaves exactly like importing `Geist`. The Figma variable only ever holds the bare name, which makes export → import → export idempotent — re-exporting appends the stack once, never twice.

`@systhemaui/core` reads the first entry as the family key, so `font-geist` and the rest of the Tailwind bridge resolve unchanged.

See [Fonts](https://docs.systhema.app/next/design/fonts.md#fallback-stacks) for how core consumes the stack.

### Colors with opacity

Figma's "control opacity at scale" (September 2026) composes a colour variable from an aliased colour plus a separate opacity argument. Its native export writes the composition beside a FLATTENED `$value`:

```jsonc title="colorSystem.default.tokens.json"
"text-muted": {
  "$type": "color",
  "$value": { "colorSpace": "srgb", "components": [0, 0, 0], "alpha": 0.25, "hex": "#000000" },
  "$extensions": {
    "com.figma.composedColor": {
      "colorArg": { "type": "alias", "alias": { "targetVariableName": "foundations/text" } },
      "opacityArg": { "type": "alias", "alias": { "targetVariableName": "foundations/mutedOpacity" } }
    }
  }
}
```

`opacityArg` is either a literal percentage (`{ "type": "number", "value": 50 }`) or an alias to a `number` token holding one. A same-collection `colorArg` carries only `targetVariableName`; a cross-collection one carries the full alias data, like `com.figma.aliasData` elsewhere. A composed token has no top-level `com.figma.aliasData`.

**The export writes that block itself.** `@figma/plugin-typings` (1.135) documents no composition — `VariableAlias` is still `{ type, id }` — but the runtime returns one as an expression:

```jsonc
// variable.valuesByMode[modeId]
{
  "type": "VARIABLE_EXPRESSION",
  "expressionFunction": "COMPOSE_COLOR",
  "expressionArguments": [{ "type": "VARIABLE_ALIAS", "id": "VariableID:4012:988" }, 25]
}
```

The exporter converts it to the format above: the colour argument becomes `colorArg` (a same-collection target by name, a cross-collection one with its full alias data), the opacity percentage becomes `opacityArg`, and `$value` is the colour resolved in the mode being exported with the opacity applied. A literal `RGBA` colour argument works the same way, as `colorArg: { "type": "color", … }`.

**On import the composition is written back** as the same expression, in a pass after the alias passes so both argument variables exist. Whether `setValueForMode` accepts an expression is not something the typings answer, so the write is attempted and its outcome reported: on success the token counts under **Composed colors set** on the result screen, and on a refusal the importer falls back to the old behaviour — an existing variable keeps its value (writing the flat colour would detach the designer's alias and freeze the opacity), a variable this run created takes the flattened colour and is reported as created detached. Either way one warning per token names the reason the API gave:

```text
foundations/text-muted: composed color (alias + opacity) cannot be written through the Plugin API yet (<message>); existing value kept
```

Those lines appear under **Warnings** on the import result screen, next to the **Errors** list.

**`com.figma.rawValue` is the probe for everything else.** The exporter reports any value shape it does not recognise instead of guessing quietly: a value that is neither an `RGBA`, nor a `VariableAlias`, nor a documented easing, nor a `COMPOSE_COLOR` expression is copied verbatim into `$extensions["com.figma.rawValue"]` (with a best-effort `$value`), and so is a `VariableAlias` carrying any key beyond `type` and `id`, and a composed colour whose arguments could not be resolved. Each one also logs a `console.warn` naming the variable and the object's keys.

[Color tokens](https://docs.systhema.app/next/design/color-tokens.md#colors-with-opacity) shows the CSS core emits for a composed color.

### Easing and timing variables

Figma's `Easing` and `Timing` variable types export in the same shape as Figma's own variable export, so the two can be mixed freely:

```jsonc title="easings.value.tokens.json"
"easeOutCubic": {
  "$type": "easing",
  "$value": "cubic-bezier(0.33, 1, 0.68, 1)"
},
"defaultTransitionSpeed": {
  "$type": "duration",
  "$value": { "value": 150, "unit": "ms" }
}
```

A named preset (Ease in, Ease out back, …) exports as the curve Figma's editor shows for it, and a custom bezier as its four points. Figma stores timing in seconds; the token carries milliseconds. The VALUE decides the type: a variable holding an easing exports as `$type: "easing"` even when Figma reports its resolved type as `STRING`, which is what older files return. Aliases behave like every other type: a same-collection alias is a `{reference}`, a cross-collection one is the resolved value plus `com.figma.aliasData`.

A **spring** (Gentle, Quick, Bouncy, Slow, custom) or a `HOLD` has no CSS curve. Its `$value` is a keyword fallback (`ease-out`, or `step-end` for hold) and the real easing rides in `$extensions["com.figma.easing"]`, which the importer prefers over the string.

**On import** a `cubic-bezier()` matching a preset becomes that preset again (so export → import → export is stable), any other curve becomes a custom bezier, and the CSS keywords `linear`, `ease`, `ease-in`, `ease-out`, `ease-in-out` map to their Figma counterpart. A duration accepts the DTCG object in `ms` or `s`, a CSS string (`150ms`, `0.15s`), or a bare number read as milliseconds. `steps()`/`linear()` easings and unit-less or unknown-unit durations are reported as import errors for that variable.

`@systhemaui/core` reads both collections and emits them as `--ease-*`, `--duration-*` and `--transition-*` custom properties; see [Motion tokens](https://docs.systhema.app/next/design/motion-tokens.md).

## What it does not contain

The plugin captures Figma's design data — variables and styles. It does NOT capture:

- **Components.** No frame-to-React-component generation. Agents pick Systhema components based on a frame's structural intent.
- **Auto-layout / constraints.** Figma's layout primitives don't map 1:1 to Systhema's `<Section>` / `<Columns>` / `<Stack>`. The translation is intent-driven, not pixel-driven.
- **Project-specific config.** Things that live in `<project>/systhema.config.ts`:
  - `manifest` overrides (subset of what Figma exports).
  - `customTokens` (project-only overrides; don't round-trip back).
  - `packages.{react|next|payload}` settings.
  - Project-only `tailwindcss` extensions.
- **Figma plugin metadata** beyond the `$extensions` block — variable comments, style descriptions, and version history don't survive the export.
- **Asset files.** Images, illustrations, and icons referenced inside Figma frames don't ship with the token export. They're handled separately (placed under `<project>/public/`).
