Defining Design Tokens
Declare your project’s design tokens in foir.config.ts to keep them in source control alongside your models and operations. The designTokens block holds a W3C Design Tokens Format Module document; foir push reconciles it to the platform.
See the Design Tokens feature page for what tokens are, how draft/published works, and the admin editor.
Quick start
import { defineConfig, defineDesignTokens } from '@eide/foir-cli/configs';
export default defineConfig({
key: 'my-storefront',
name: 'My Storefront',
designTokens: defineDesignTokens({
color: {
$type: 'color',
brand: {
primary: { $value: '#2c4433', $description: 'Deep forest green' },
accent: { $value: '#d4553a' },
},
},
spacing: {
$type: 'dimension',
xs: { $value: '8px' },
sm: { $value: '12px' },
md: { $value: '16px' },
lg: { $value: '24px' },
},
}),
});Push it:
foir push --publish--publish applies the tokens to the draft channel and promotes them to the published channel. Without it, tokens land on draft and your storefront keeps serving the last published snapshot.
The designTokens block
The block is a W3C document — top-level keys are token groups, leaves carry $value and optionally $description, $extensions. Group-level $type cascades to leaves so you don’t repeat the type on every entry.
designTokens: {
color: {
$type: 'color',
brand: {
primary: { $value: '#2c4433' },
},
},
font: {
family: {
$type: 'fontFamily',
display: { $value: ['Inter Display', 'system-ui', 'sans-serif'] },
body: { $value: ['Inter', 'system-ui', 'sans-serif'] },
},
weight: {
$type: 'fontWeight',
regular: { $value: 400 },
bold: { $value: 700 },
},
size: {
$type: 'dimension',
body: { $value: '1rem' },
display1: { $value: 'clamp(2.5rem, 6vw, 5rem)' },
},
lineHeight: {
$type: 'number',
body: { $value: 1.6 },
display: { $value: 0.95 },
},
letterSpacing: {
$type: 'dimension',
normal: { $value: '0' },
tight: { $value: '-0.02em' },
},
},
typography: {
$type: 'typography',
body: {
$value: {
fontFamily: '{font.family.body}',
fontSize: '{font.size.body}',
fontWeight: '{font.weight.regular}',
lineHeight: '{font.lineHeight.body}',
letterSpacing: '{font.letterSpacing.normal}',
},
$extensions: { 'foir.label': 'Body', 'foir.tag': 'p' },
},
},
spacing: {
$type: 'dimension',
xs: { $value: '8px' },
md: { $value: '16px' },
lg: { $value: '24px' },
},
radius: {
$type: 'dimension',
none: { $value: '0' },
md: { $value: '8px' },
full: { $value: '9999px' },
},
shadow: {
$type: 'shadow',
elevated: {
$value: [
{ offsetX: '0px', offsetY: '1px', blur: '2px', spread: '0px', color: '#0000001a' },
{ offsetX: '0px', offsetY: '4px', blur: '8px', spread: '0px', color: '#00000033' },
],
},
},
border: {
$type: 'border',
hairline: {
$value: { width: '1px', style: 'solid', color: '{color.brand.primary}' },
},
},
}Group reference
Every group key is optional. Include only what your project needs.
| Group | $type | $value shape | Notes |
|---|---|---|---|
color.* | color | string | Any CSS color: hex, rgb(), hsl(), named color. |
font.family.* | fontFamily | string[] | Primary + fallbacks. |
font.weight.* | fontWeight | number | 100–900. |
font.size.* | dimension | string | Any CSS length: 1rem, 16px, clamp(...). |
font.lineHeight.* | number | number | Unitless multiplier. |
font.letterSpacing.* | dimension | string | 0, 0.02em, -1px. |
typography.* | typography | object | { fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, textTransform? }. Reference primitives with {path}. |
spacing.* | dimension | string | Scale steps for padding, margin, gap. |
radius.* | dimension | string | Corner-radius scale. |
shadow.* | shadow | object or object[] | Single layer or list of layers. Each layer: { offsetX, offsetY, blur, spread, color, inset? }. |
border.* | border | object | { width, style, color }. |
Top-level groups that don’t match one of the names above are accepted and stored verbatim — Foir surfaces them under source for build tooling but doesn’t expose them through a typed resolved array.
References
Any $value (or any field inside a composite $value) accepts the {path.to.token} reference syntax. References resolve recursively; cycles and unknown references are rejected at save time.
{
color: {
$type: 'color',
brand: { primary: { $value: '#2c4433' } },
surface: { accent: { $value: '{color.brand.primary}' } }, // → #2c4433
},
border: {
$type: 'border',
stamp: {
$value: { width: '1px', style: 'solid', color: '{color.brand.primary}' },
},
},
}Extensions
Use $extensions to attach editor metadata without affecting the resolved value.
| Extension | Applies to | Purpose |
|---|---|---|
foir.label | any token | Overrides the editor display name. |
foir.tag | typography tokens | Default semantic HTML tag (p, h1–h4, blockquote). |
foir.textTransform | typography tokens | uppercase / lowercase / capitalize. Surfaces as textTransform on the resolved value. |
foir.fontStyle | typography tokens | italic / normal. |
display1: {
$value: { /* ... */ },
$extensions: {
'foir.label': 'Display 1',
'foir.tag': 'h1',
'foir.textTransform': 'uppercase',
},
}Helper functions
defineDesignTokens
Identity helper that gives you TypeScript IntelliSense on the designTokens shape.
import { defineDesignTokens } from '@eide/foir-cli/configs';
const tokens = defineDesignTokens({
color: { $type: 'color', brand: { primary: { $value: '#2c4433' } } },
});The interface is permissive ([key: string]: unknown), so additional groups beyond color, font, and typography — like spacing, radius, shadow, border — are accepted at runtime.
Push behaviour
foir push reconciles designTokens alongside the rest of your config:
- The document is validated at save time using the same rules as the admin editor — push fails if you introduce a cycle or an unknown reference.
- Tokens are written to the draft channel. Pass
--publishto also promote them to published in the same push. - Re-running
foir pushis idempotent — the platform stores the document verbatim, so pushing the samedesignTokensblock twice is a no-op.
# Apply to draft only (storefront keeps serving the last published snapshot)
foir push
# Apply and publish in one step
foir push --publishSee the CLI command reference for foir design-tokens subcommands that operate on tokens without going through a full foir push.