Custom token manifest
Declare custom tabs with TabConfig, TierConfig, and TierItem — including all value-kind variants.
The panel’s tab strip is driven by PanelConfig.tabs, a host-supplied array of
TabConfig entries. Each tab owns its tiers; each tier owns its items. There is
no global manifest object — tokens live inside the tab that edits them.
This recipe walks through the day-to-day moves: a minimal single-tier tab, every
value-kind variant, optional group labels, and advancedTiers disclosure.
For the full type contract see Token manifest reference.
Minimal single-tier tab
The smallest valid tab has one tier and at least one item. Every TierItem
carries a discriminated type field instead of flat control/min/max
properties — the kind drives which editor the panel renders.
// src/lib/my-tabs.ts
import type { PanelConfig } from '@takazudo/zdtp';
type TabConfig = PanelConfig['tabs'][number];
const spacingTab: TabConfig = {
id: 'spacing',
label: 'Spacing',
tiers: [
{
id: 'raw',
label: 'Spacing',
items: [
{
id: 'myapp-spacing-md',
cssVar: '--myapp-spacing-md',
label: 'Medium',
group: 'core',
default: '1rem',
type: { kind: 'length', min: 0, max: 4, step: 0.125, unit: 'rem' },
},
{
id: 'myapp-spacing-lg',
cssVar: '--myapp-spacing-lg',
label: 'Large',
group: 'core',
default: '1.5rem',
type: { kind: 'length', min: 0, max: 6, step: 0.125, unit: 'rem' },
},
],
},
],
};
export const myTabs: readonly TabConfig[] = [spacingTab];
Pass the array to PanelConfig.tabs:
// src/lib/my-panel-config.ts
import type { PanelConfig } from '@takazudo/zdtp';
import { myTabs } from './my-tabs';
export const myPanelConfig: PanelConfig = {
storagePrefix: 'myapp-design-token-panel',
consoleNamespace: 'myapp',
modalClassPrefix: 'myapp-design-token-panel-modal',
schemaId: 'myapp-design-tokens/v1',
exportFilenameBase: 'myapp-design-tokens',
tabs: myTabs,
};
Value-kind variants
TierItem.type is a discriminated union — pick the variant that matches what
the CSS variable holds.
length — slider with unit
Use for numeric CSS lengths. The panel renders a slider bounded by min/max
with step increments and appends unit to the emitted value.
{
id: 'myapp-spacing-md',
cssVar: '--myapp-spacing-md',
label: 'Medium spacing',
group: 'core',
default: '1rem',
type: { kind: 'length', min: 0, max: 4, step: 0.125, unit: 'rem' },
}
number — unitless slider
Like length but emits a bare number (no unit string appended). Useful for
line-height, opacity, font-weight, and similar unitless properties.
{
id: 'myapp-line-height-body',
cssVar: '--myapp-line-height-body',
label: 'Body line height',
group: 'rhythm',
default: '1.6',
type: { kind: 'number', min: 1, max: 2.5, step: 0.05 },
}
select — fixed option list
Renders a dropdown. options is the exhaustive list of allowed values; the
panel writes whichever option the user picks.
{
id: 'myapp-font-family-body',
cssVar: '--myapp-font-family-body',
label: 'Body font',
group: 'family',
default: 'system-ui',
type: { kind: 'select', options: ['system-ui', 'Inter, sans-serif', 'Georgia, serif'] },
}
text — free-form CSS string
Renders a plain text input. The value is written to the CSS variable verbatim.
Use for anything that does not fit a slider or dropdown: easing functions,
cubic-bezier(...) values, composite shorthands, etc.
{
id: 'myapp-easing-default',
cssVar: '--myapp-easing-default',
label: 'Default easing',
group: 'motion',
default: 'cubic-bezier(0.4, 0, 0.2, 1)',
type: { kind: 'text' },
}
color — hex color swatch
Renders a color picker. The value is a CSS hex string (#rrggbb).
{
id: 'myapp-palette-brand',
cssVar: '--myapp-palette-brand',
label: 'Brand',
group: 'palette',
default: '#2d6cdf',
type: { kind: 'color' },
}
Group ids and ordering
group on a TierItem is a plain string — coin whatever ids fit your
design system. Groups are just visual section dividers inside a tier; the
panel renders a header for each unique group value it encounters.
Items appear in the order they are declared in the items array. If you want
groups to cluster together, sort by group before declaring the array. There is
no separate groupOrder field on TabConfig — declaration order is the
render order.
const items = [
// core group first
{ id: 'myapp-spacing-sm', ..., group: 'core' },
{ id: 'myapp-spacing-md', ..., group: 'core' },
// component group after
{ id: 'myapp-spacing-card', ..., group: 'components' },
];
Advanced tiers (disclosure)
Set advancedTiers to an array of tier ids that should render inside the
per-tab “Advanced” disclosure rather than in the always-visible list. This is
useful for raw scale tiers that expert users might want to tweak but are
normally hidden behind the semantic layer.
const fontTab: TabConfig = {
id: 'font',
label: 'Font',
advancedTiers: ['raw'], // hides the raw scale tier behind disclosure
tiers: [
{
id: 'raw',
label: 'Type Scale',
items: [
{ id: 'myapp-scale-sm', cssVar: '--myapp-scale-sm', label: 'Scale SM', group: 'scale', default: '0.875rem', type: { kind: 'length', min: 0.5, max: 2, step: 0.0625, unit: 'rem' } },
{ id: 'myapp-scale-base', cssVar: '--myapp-scale-base', label: 'Scale Base', group: 'scale', default: '1rem', type: { kind: 'length', min: 0.5, max: 2, step: 0.0625, unit: 'rem' } },
{ id: 'myapp-scale-lg', cssVar: '--myapp-scale-lg', label: 'Scale LG', group: 'scale', default: '1.25rem', type: { kind: 'length', min: 0.75, max: 3, step: 0.0625, unit: 'rem' } },
],
},
{
id: 'semantic',
label: 'Semantic Roles',
referencesTier: 'raw',
items: [
{ id: 'myapp-text-body', cssVar: '--myapp-text-body', label: 'Body', group: 'roles', default: 'myapp-scale-base', type: { kind: 'text' } },
{ id: 'myapp-text-heading', cssVar: '--myapp-text-heading', label: 'Heading', group: 'roles', default: 'myapp-scale-lg', type: { kind: 'text' } },
],
},
],
};
The referencesTier field tells the panel that items in the semantic tier
hold ids from the raw tier as their values, not raw CSS values. The apply
pipeline emits var(--myapp-scale-base) instead of a literal value when it
writes the semantic role to disk. See the
Multi-tier tokens recipe for a full
worked example.
Read-only display rows
Set readonly: true on items you want to show in the panel without allowing
edits. The apply pipeline skips read-only items in both directions.
{
id: 'myapp-computed-max-width',
cssVar: '--myapp-computed-max-width',
label: 'Max width (computed)',
group: 'layout',
default: 'min(100%, 64rem)',
type: { kind: 'text' },
readonly: true,
}
Pill toggle on a length slider
A pill toggle surfaces a single “special” sentinel value alongside the slider.
The user can snap to it with one click and return to a numeric value via a
toggle. Common use case: infinite border-radius (9999px).
{
id: 'myapp-radius-button',
cssVar: '--myapp-radius-button',
label: 'Button radius',
group: 'shape',
default: '0.375rem',
type: { kind: 'length', min: 0, max: 1, step: 0.0625, unit: 'rem' },
pill: { value: '9999px', customDefault: '0.375rem' },
}
pill.value is the sentinel emitted when the user clicks the pill.
pill.customDefault is the numeric value restored when they click “Custom”.
Related
- Multi-tier tokens — 2-tier raw + semantic
setup with
referencesTierandvar()resolution. - Token manifest reference — full
TierItem,TierConfig,TabConfig, andTierValueKindinterfaces. - <code>configurePanel</code> reference — where
tabsplugs in. - Apply pipeline setup — wiring apply-to-disk for custom prefixes.