Color cluster
How the Color tab derives a ColorClusterDataConfig from a TabConfig with colorExtras, the palette + semantic tier model, ColorScheme, and scheme presets.
The Color tab — palette + base roles + semantic table + scheme list — is driven by a TabConfig with the reserved id 'color'. The tab’s tiers hold the palette and semantic data as TierItem arrays; the colorExtras field on the same TabConfig carries the non-tier metadata (base roles, color schemes, panel settings). Internally the panel bridges this into a ColorClusterDataConfig via resolveColorClusterFromTab.
This page explains the Color-tab TabConfig structure, the ColorClusterExtras shape, the ColorScheme type, multi-cluster support, and host-supplied scheme presets.
Color tab TabConfig structure
A TabConfig for the Color tab follows the same shape as any other tab, with two constraints:
idmust be'color'(primary) or'color-secondary'(secondary).colorExtrasmust be present — it carries the non-tier metadata the color apply pipeline needs.
Palette tier
The palette tier is the first tier whose items all have type.kind === 'color' and no referencesTier. Each item represents one palette slot; its cssVar is the CSS custom property written on apply (e.g. --myapp-palette-0).
The internal bridge derives paletteCssVarTemplate by replacing the trailing digit sequence of the first item’s cssVar with {n}:
--myapp-palette-0 → --myapp-palette-{n}
Semantic tier
The semantic tier is the first tier whose referencesTier points at the palette tier’s id. Each semantic item’s default holds the id of a palette item; that id is looked up to produce the default palette index.
When the user overrides a semantic token, the apply pipeline reads the referenced palette item’s cssVar and emits var(--that-cssVar) for the semantic slot.
Minimal example
import type { TabConfig } from '@takazudo/zdtp';
export const colorTab: TabConfig = {
id: 'color',
label: 'Color',
tiers: [
{
id: 'palette',
label: 'Palette',
items: [
{ id: 'p0', cssVar: '--myapp-palette-0', label: 'P0', default: '#1a1a2e', type: { kind: 'color' } },
{ id: 'p1', cssVar: '--myapp-palette-1', label: 'P1', default: '#16213e', type: { kind: 'color' } },
{ id: 'p2', cssVar: '--myapp-palette-2', label: 'P2', default: '#0f3460', type: { kind: 'color' } },
{ id: 'p3', cssVar: '--myapp-palette-3', label: 'P3', default: '#e94560', type: { kind: 'color' } },
],
},
{
id: 'semantic',
label: 'Semantic',
referencesTier: 'palette',
items: [
{ id: 'bg', cssVar: '--myapp-color-bg', label: 'Background', default: 'p0', type: { kind: 'color' } },
{ id: 'surface', cssVar: '--myapp-color-surface', label: 'Surface', default: 'p1', type: { kind: 'color' } },
{ id: 'accent', cssVar: '--myapp-color-accent', label: 'Accent', default: 'p3', type: { kind: 'color' } },
],
},
],
colorExtras: {
id: 'myapp',
baseRoles: { background: '--myapp-palette-0', foreground: '--myapp-palette-3' },
baseDefaults: { background: 0, foreground: 3 },
defaultShikiTheme: 'github-dark',
colorSchemes: {
'Default Dark': {
background: 0, foreground: 3, cursor: 3, selectionBg: 2, selectionFg: 3,
palette: ['#1a1a2e', '#16213e', '#0f3460', '#e94560'],
shikiTheme: 'github-dark',
},
},
panelSettings: {
colorScheme: 'Default Dark',
colorMode: false,
},
},
};
ColorClusterExtras
The colorExtras field on a color TabConfig carries every piece of metadata that does not fit into the tier model.
export interface ColorClusterExtras {
/** Stable id forwarded to internal cluster helpers. */
id: string;
/** Optional label for Color-tab section headings. Falls back to `id.toUpperCase()`. */
label?: string;
/** CSS custom-property names for terminal base 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. Inert when no shiki integration. */
defaultShikiTheme: string;
/** Bundled color-scheme registry keyed by display name. Pass `{}` when unused. */
colorSchemes: Record<string, ColorScheme>;
/** Panel-level scheme settings. */
panelSettings: ClusterPanelSettings;
}
export type BaseRoleKey = 'background' | 'foreground' | 'cursor' | 'selectionBg' | 'selectionFg';
baseRoles is a partial map — a cluster declares only the terminal roles its design system surfaces. An empty map is legal; only declared roles emit CSS writes on apply.
ColorScheme
export type ColorRef = number | string;
export interface ColorScheme {
background: ColorRef;
foreground: ColorRef;
cursor: ColorRef;
selectionBg: ColorRef;
selectionFg: ColorRef;
palette: readonly string[]; // length must equal the palette tier's item count
shikiTheme: string;
semantic?: Record<string, ColorRef>; // keys must be a subset of the semantic tier's item ids
}
A ColorScheme is a fully-resolved snapshot: the palette hex values plus role assignments that pin which palette indices the base and semantic roles point at.
ColorRef
- A number references an index into
palette(e.g.2→palette[2]). - A string is a literal color value (hex like
#33ff33,rgb(...), etc.). The shorthand"bg"resolves to the scheme’s background;"fg"resolves to the foreground.
Palette length invariant
ColorScheme.palette.length MUST equal the number of items in the palette tier. Schemes with mismatched palette lengths are rejected at init time.
Semantic key invariant
The keys of ColorScheme.semantic MUST be a subset of the semantic tier’s item ids. A scheme cannot introduce new semantic tokens — the tier model is the authoritative vocabulary.
ClusterPanelSettings
Carried inside ColorClusterExtras.panelSettings.
export interface ClusterPanelSettings {
/** Scheme name to seed state from when `colorMode` is `false`. */
colorScheme: string;
/**
* `false` to disable light/dark UI; an object to honour `data-theme` on
* `<html>` and switch schemes on init.
*/
colorMode: false | { defaultMode: 'light' | 'dark'; lightScheme: string; darkScheme: string };
}
Internal bridge: resolveColorClusterFromTab
resolveColorClusterFromTab(tab) derives a ColorClusterDataConfig from any TabConfig that has colorExtras. It is called automatically by the panel when it processes the 'color' and 'color-secondary' tabs.
Hosts do not call this directly — it is exposed from cluster-config for testing and advanced host tooling.
import { resolveColorClusterFromTab } from '@takazudo/zdtp';
const cluster = resolveColorClusterFromTab(colorTab);
// cluster.paletteSize, cluster.paletteCssVarTemplate, cluster.semanticDefaults, ...
Returns undefined when the tab has no colorExtras.
Multi-cluster support
Supply a second TabConfig with id: 'color-secondary' to enable a secondary color section:
configurePanel({
// ...
tabs: [
colorTab, // id: 'color'
colorSecondaryTab, // id: 'color-secondary'
// ... other tabs
],
});
| Secondary tab state | Meaning |
| --- | --- |
| Not present in tabs | Secondary section hidden; apply / clear skip secondary code paths. |
| Present with colorExtras | Secondary section rendered and applied independently. |
Resolution is performed via resolveSecondaryColorClusterFromTabs(tabs) exported from panel-config.
Host-supplied scheme presets
PanelConfig.colorPresets is an optional, host-supplied preset map surfaced in the Color tab “Scheme…” dropdown. Defaults to {} — the package ships zero presets.
| colorPresets value | Effect |
| --- | --- |
| undefined or {} | Only colorExtras.colorSchemes populates the dropdown. |
| Record<string, ColorScheme> | Each key appears below the cluster’s bundled schemes, sorted alphabetically. |
Merge order in the dropdown
<option disabled>Scheme...</option>
... colorExtras.colorSchemes (insertion order) ...
<hr />
... colorPresets (alphabetical) ...
On key collision, the cluster’s bundled scheme wins for the load lookup. The dropdown renders both entries; visually deduplicating is out of scope.
Lazy attachment via setPanelColorPresets()
Large preset libraries can be deferred to avoid inflating the inline SSR config blob:
import { setPanelColorPresets } from '@takazudo/zdtp';
void import('./large-preset-library').then(({ presets }) => {
setPanelColorPresets(presets);
});
See <code>setPanelColorPresets</code> for the full contract.
Apply behaviour
When the user clicks Apply for the Color tab, the pipeline:
- Iterates palette items (palette tier): writes
paletteTier.items[i].cssVar←palette[i]for each slot. - Iterates
baseRoles: writescssName←palette[state[roleKey]]. Absent roles emit no writes. - Iterates semantic items: resolves each item’s override (a palette item id) to the palette item’s
cssVar, emitsvar(--that-cssVar)into the semantic slot.
clearAppliedStyles removes every CSS property that the cluster could have set (palette + base roles + semantic).
Cross-references
- <code>PanelConfig.
tabs</ code> — where the Color TabConfigis supplied. - Token tiers — full
TabConfig/TierConfig/TierItemtype reference. - Apply pipeline — how tier resolver + cross-tier refs drive CSS-var emission.