Token tiers (tab manifest)
TabConfig, TierConfig, TierItem, TierValueKind, PillSpec, and ColorClusterExtras — the types that define every editable tab in the panel.
Concept overview
This page is the type-by-type pin-down of the tier model. If you want to understand the design rationale and the literal / reference tier relationship first, read The abstract token-tier model.
The panel renders its tab strip from a flat array of TabConfig objects passed as PanelConfig.tabs. Each tab owns one or more tiers of editable rows; a tier can hold literal values or references to another tier's items. This page pins every public type in the tier model.
TierValueKind
Discriminated union that controls which editor widget appears for a row.
export type TierValueKind =
| { kind: 'length'; step: number; unit: string }
| { kind: 'number'; step: number }
| { kind: 'select'; options: readonly string[] }
| { kind: 'text' }
| { kind: 'color' }
| { kind: 'cursor' }
| { kind: 'content' }
| { kind: 'mask-image' };kind | Description | Editor | Extra required fields |
|---|---|---|---|
'length' | Dimension values with a unit | Text input with a unit suffix (e.g. rem, px) | step, unit |
'number' | Unitless numeric values | Unconstrained text input | step |
'select' | Predefined option sets | Dropdown | options (non-empty array of strings) |
'text' | Free-form CSS expressions (easing curves, font families, raw strings) | Free-form text input | — |
'color' | Color values — used for palette tiers in the Color tab | Color picker | — |
'cursor' | cursor property values (keywords like pointer, crosshair, or url(...) <fallback>) | Free-form text input | — |
'content' | content property values for pseudo-elements (quoted strings, none, normal, url(...), generated-content functions) | Free-form text input | — |
'mask-image' | mask-image property values (none, url(...), gradient functions) | Free-form text input | — |
All items in a given TierConfig.items array MUST share the same kind. Mixing kinds inside a single tier is rejected at validation time.
PillSpec
Optional pill toggle that supplements the main input for a single TierItem.
export interface PillSpec {
/** Sentinel value emitted when the pill is ON (e.g. `"9999px"` for border-radius-full). */
value: string;
/** Default CSS value used when the pill is OFF and no override is active. */
customDefault: string;
}When a TierItem carries a pill, the tier resolver interprets the override map as follows:
Override equals
pill.value→ emit the pill sentinel verbatim.No override → use
pill.customDefaultas the baseline (notitem.default).item.defaultis only reached when neither an override nor acustomDefaultapply.
TierItem
One editable row inside a tier.
export interface TierItem {
/** Stable id used as the key in persisted state. Rename = state loss. */
id: string;
/** CSS custom property written to `:root` (must start with `--`). */
cssVar: string;
/** Human-visible label rendered in the panel row. */
label: string;
/** Default CSS string value — used when no override is active. */
default: string;
/** Discriminated union controlling the edit widget. */
type: TierValueKind;
/** Opt-in sentinel-value toggle. */
pill?: PillSpec;
/** Display-only flag — the row is rendered but not editable. */
readonly?: true;
}No `group` field
TierItem.group (a sub-heading key inside a tier) was removed in #148 — the panel's one-tier-one-heading policy requires each tier to render exactly one section heading. A tier that conceptually needs sub-grouping is split into multiple TierConfig entries instead. Example manifests must not reference group.
Field reference
| Field | Required | Notes |
|---|---|---|
id | yes | Stable key in the persisted override map. Renaming breaks existing user state unless legacyIdRenameMap is configured. |
cssVar | yes | The CSS custom property written to :root. Must start with -- and be non-empty after the prefix; validated by assertValidPanelConfig. |
label | yes | Display label inside the panel row. |
default | yes | Fallback CSS string emitted when no override is active. |
type | yes | Controls which editor renders for this row. |
pill | no | Adds an opt-in sentinel toggle alongside the main editor. |
readonly | no | When true, the row is shown but not editable and is skipped by the apply pipeline. |
TierConfig
A named group of TierItem entries within a tab.
export interface TierConfig {
/** Stable id — must be unique within the parent `TabConfig`. */
id: string;
/** Human-visible label (e.g. "Raw values", "Semantic"). */
label: string;
/** The editable rows this tier owns. All items must share the same `kind`. */
items: readonly TierItem[];
/**
* When set, this tier holds cross-tier references.
* Each item's override value is interpreted as the id of an item in the
* tier named by this field. The apply pipeline emits
* `var(--that-tier-item-cssVar)` rather than the raw value string.
*/
referencesTier?: string;
/**
* Marks this tier as a SEMANTIC tier on the Color tab — its items hold
* `SemanticValue` mappings (index, literal, per-mode literal, or a
* cross-tab ramp reference) rather than raw palette entries, so the
* panel never mistakes it for the palette tier even when its items are
* `kind: 'color'`. A tier with `semantic: true` and no sibling palette
* tier is a **lone semantic tier** — the Color tab renders it with no
* palette grid at all. Additive: omitting `semantic` preserves the
* pre-#459 structural palette-detection behavior.
*/
semantic?: true;
/**
* Cross-tab ramp-source declaration for a `semantic: true` tier owned by a
* tab whose id is exactly `color` or `color-secondary`; other owning tab
* ids are rejected. Each entry names an allowed source tier `id` and an
* optional source `tab` id (omitted `tab` means "this tab"). Validated up
* front by `assertValidPanelConfig` before any row renders.
*/
referencesRamps?: readonly { tab?: string; tier: string }[];
}A tier with referencesTier is called a reference tier. Its items don't hold direct CSS values — they hold the id of a source item in another (literal) tier. The referencesTier value must be the id of a tier that exists in the same TabConfig. The tier kinds must match (e.g. both 'color' or both 'length').
semantic + referencesRamps are Color-tab concepts
semantic is a Color-tab concept, and referencesRamps is valid only on a tier inside a TabConfig whose id is exactly 'color' or 'color-secondary'; assertValidPanelConfig rejects every other owning tab id. For the full behavioral contract — the SemanticValue union, how a lone semantic tier renders, and a worked cross-tab example — see Color cluster § Semantic-only tier.
TabConfig
The top-level unit — one tab in the panel's strip.
export interface TabConfig {
/** Stable id. Reserved ids dispatch to built-in tab components. */
id: string;
/** Human-visible tab label. */
label: string;
/** Ordered tiers rendered inside the tab. */
tiers: readonly TierConfig[];
/**
* Color-tab metadata. Required for the reserved `'color'` and
* `'color-secondary'` ids — ignored (and unnecessary) for all other ids.
*/
colorExtras?: ColorClusterExtras;
}No `advancedTiers` field
TabConfig.advancedTiers (a per-tab "Advanced" <details> disclosure) was removed in #148 — the panel's no-progressive-disclosure policy requires every tier to render flat, with no collapsed sections. If a tab has many tokens, render them flat in tier-section order instead. Example manifests must not reference advancedTiers.
Reserved tab ids
id | Dispatches to |
|---|---|
'color' | Primary color tab — palette + base roles + semantic table. Requires colorExtras. |
'color-secondary' | Secondary color tab. Requires colorExtras. |
'font' | Typography tab. |
'spacing' | Spacing tab. |
'size' | Size tab. |
| Any other string | GenericTab — renders the tab's tiers using kind-appropriate editors. |
ColorClusterExtras
Non-tier metadata required by the Color tab. Sits on TabConfig.colorExtras for any tab with a reserved color id.
export interface ColorClusterExtras {
/** Stable id forwarded to the internal ColorClusterDataConfig bridge. */
id: string;
/** Optional label for Color-tab section headings. Falls back to `id.toUpperCase()`. */
label?: string;
/** CSS custom-property names for base terminal roles. */
baseRoles: Partial<Record<BaseRoleKey, string>>;
/** Fallback palette indices when a scheme omits a base role. */
baseDefaults: Partial<Record<BaseRoleKey, number>>;
/** Fallback shikiTheme name when a scheme lacks one. */
defaultShikiTheme: string;
/** Bundled color-scheme registry keyed by display name. */
colorSchemes: Record<string, ColorScheme>;
/** Panel-level scheme settings (seed scheme, optional light/dark mode). */
panelSettings: ClusterPanelSettings;
}
export type BaseRoleKey = 'background' | 'foreground' | 'cursor' | 'selectionBg' | 'selectionFg';Example: single-tier spacing tab
A simple tab with one literal tier — every item is a length token:
import type { TabConfig } from '@takazudo/zdtp';
export const spacingTab: TabConfig = {
id: 'spacing',
label: 'Spacing',
tiers: [
{
id: 'base',
label: 'Base spacing',
items: [
{ id: 'sp-xs', cssVar: '--myapp-spacing-xs', label: 'XS', default: '0.25rem', type: { kind: 'length', step: 0.125, unit: 'rem' } },
{ id: 'sp-sm', cssVar: '--myapp-spacing-sm', label: 'SM', default: '0.5rem', type: { kind: 'length', step: 0.125, unit: 'rem' } },
{ id: 'sp-md', cssVar: '--myapp-spacing-md', label: 'MD', default: '1rem', type: { kind: 'length', step: 0.25, unit: 'rem' } },
],
},
],
};Example: two-tier easing tab (literal + reference)
A generic tab where a semantic tier references a raw tier. The panel emits var(--myapp-easing-ease-in) for a semantic item rather than a raw cubic-bezier string:
import type { TabConfig } from '@takazudo/zdtp';
export const easingTab: TabConfig = {
id: 'easing',
label: 'Easing',
tiers: [
{
id: 'raw',
label: 'Raw values',
items: [
{ id: 'ease-in', cssVar: '--myapp-easing-ease-in', label: 'Ease in', default: 'cubic-bezier(0.42, 0, 1, 1)', type: { kind: 'text' } },
{ id: 'ease-out', cssVar: '--myapp-easing-ease-out', label: 'Ease out', default: 'cubic-bezier(0, 0, 0.58, 1)', type: { kind: 'text' } },
{ id: 'ease-inout', cssVar: '--myapp-easing-ease-inout', label: 'Ease in-out',default: 'cubic-bezier(0.42, 0, 0.58, 1)', type: { kind: 'text' } },
],
},
{
id: 'semantic',
label: 'Semantic aliases',
// referencesTier causes the apply pipeline to emit var(--myapp-easing-*)
// rather than the raw cubic-bezier string.
referencesTier: 'raw',
items: [
{ id: 'tab-open', cssVar: '--myapp-transition-tab-open', label: 'Tab open', default: 'ease-in', type: { kind: 'text' } },
{ id: 'tab-close', cssVar: '--myapp-transition-tab-close', label: 'Tab close', default: 'ease-out', type: { kind: 'text' } },
{ id: 'modal', cssVar: '--myapp-transition-modal', label: 'Modal', default: 'ease-inout', type: { kind: 'text' } },
],
},
],
};In this setup, overriding tab-open to 'ease-out' causes the apply pipeline to emit --myapp-transition-tab-open: var(--myapp-easing-ease-out) rather than the literal easing string.
Cross-references
PanelConfig.tabs— whereTabConfig[]is plugged in.Color cluster — how the Color tab derives a
ColorClusterDataConfigfrom aTabConfigwithcolorExtras.Apply pipeline — how the tier resolver turns overrides and cross-tier refs into CSS-var writes.
Grouped palette tab — worked example of a standalone Palette tab using multiple
kind: 'color'tiers withoutcolorExtras.