Custom token manifest
Add or remove tabs, coin custom group ids, and control group ordering in the token manifest.
The panel’s spacing / typography / size tabs are driven by a host-supplied
TokenManifest. The shape is intentionally open — TokenGroup is just
string, so you can coin your own group ids without forking the package.
This recipe walks through the moves you actually do day-to-day.
For the full type contract, see Token manifest reference.
Minimal manifest
Every TokenManifest must have all four arrays present (any can be empty).
A token with control: 'slider' (the default when omitted) needs min,
max, step, and unit.
// src/lib/my-tokens.ts
import type { TokenManifest, TokenDef } from '@takazudo/zudo-design-token-panel';
const SPACING_TOKENS: readonly TokenDef[] = [
{
id: 'sp-md',
cssVar: '--myapp-spacing-md',
label: 'Medium',
group: 'core',
default: '1rem',
min: 0,
max: 4,
step: 0.125,
unit: 'rem',
},
{
id: 'sp-lg',
cssVar: '--myapp-spacing-lg',
label: 'Large',
group: 'core',
default: '1.5rem',
min: 0,
max: 6,
step: 0.125,
unit: 'rem',
},
];
export const myTokens: TokenManifest = {
spacing: SPACING_TOKENS,
typography: [],
size: [],
color: [],
};
Coining your own group ids
TokenGroup is string, not a closed union. You can add custom group ids
freely — but you almost always want to also populate groupTitles so the
section header renders something nicer than the raw id.
const SPACING_TOKENS: readonly TokenDef[] = [
// ...existing core tokens...
{
id: 'sp-card',
cssVar: '--myapp-spacing-card',
label: 'Card padding',
group: 'cards', // ← brand-new group id
default: '1.25rem',
min: 0,
max: 4,
step: 0.0625,
unit: 'rem',
},
];
export const myTokens: TokenManifest = {
spacing: SPACING_TOKENS,
typography: [],
size: [],
color: [],
// Render order — anything not listed falls back to the package default.
spacingGroupOrder: ['core', 'cards'],
// Human-readable section headers.
groupTitles: {
core: 'Core spacing',
cards: 'Card-specific spacing',
},
};
📝 Note
TokenGroup opens the union deliberately. Section headers fall back to
the raw group id if you forget groupTitles, so a typo will show up
visually rather than as a TypeScript error.
Different control types
Beyond the default slider, two more controls are supported.
const TYPOGRAPHY_TOKENS: readonly TokenDef[] = [
// 1. Select — fixed list of allowed values.
{
id: 'font-family-body',
cssVar: '--myapp-font-family-body',
label: 'Body font',
group: 'family',
default: 'system-ui',
min: 0,
max: 0,
step: 0,
unit: '',
control: 'select',
options: ['system-ui', 'Inter, sans-serif', 'Georgia, serif'],
},
// 2. Text — free-form CSS string.
{
id: 'line-height-relaxed',
cssVar: '--myapp-line-height-relaxed',
label: 'Relaxed line height',
group: 'rhythm',
default: '1.6',
min: 0,
max: 0,
step: 0,
unit: '',
control: 'text',
},
// 3. Slider with pill toggle — exposes a "max" sentinel value.
{
id: 'radius-button',
cssVar: '--myapp-radius-button',
label: 'Button radius',
group: 'shape',
default: '0.375rem',
min: 0,
max: 1,
step: 0.0625,
unit: 'rem',
pill: { value: '9999px', customDefault: '0.375rem' },
},
];
💡 Tip
For non-slider controls (select, text), set min, max, step to
sensible filler values (e.g. 0) — they are required by the TokenDef
shape but ignored by the renderer.
Hiding rows behind “Advanced”
Set advanced: true on rows that should live inside the per-tab disclosure
rather than the always-visible list:
{
id: 'sp-rare',
cssVar: '--myapp-spacing-rare',
label: 'Rarely-used spacing',
group: 'core',
default: '0.0625rem',
min: 0,
max: 1,
step: 0.0625,
unit: 'rem',
advanced: true,
},
Read-only display rows
Use readonly: true for tokens you want to display in the panel without
allowing edits. The apply pipeline skips read-only tokens in both directions.
{
id: 'computed-content-width',
cssVar: '--myapp-computed-content-width',
label: 'Content width (computed)',
group: 'layout',
default: 'min(100%, 64rem)',
min: 0,
max: 0,
step: 0,
unit: '',
control: 'text',
readonly: true,
},
Color tab: empty array vs cluster-driven
For color, the package expects you to drive the Color tab through a
ColorClusterConfig, not through per-token rows in tokens.color. Cluster-
driven hosts ship color: []; the field stays required so a cluster-less
host could in principle provide rows here.
export const myTokens: TokenManifest = {
spacing: SPACING_TOKENS,
typography: TYPOGRAPHY_TOKENS,
size: SIZE_TOKENS,
color: [], // ← cluster-driven; see custom-color-cluster recipe.
};
Related
- Token manifest reference — full
TokenDefandTokenManifestinterfaces, helpers, and apply semantics. - Custom color cluster — the cluster-driven Color tab.
- <code>configurePanel</code> reference — where the manifest plugs in.