Zudo Token Panel

Type to search...

to open search from anywhere

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:

  1. id must be 'color' (primary) or 'color-secondary' (secondary).
  2. colorExtras must 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. 2palette[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:

  1. Iterates palette items (palette tier): writes paletteTier.items[i].cssVarpalette[i] for each slot.
  2. Iterates baseRoles: writes cssNamepalette[state[roleKey]]. Absent roles emit no writes.
  3. Iterates semantic items: resolves each item’s override (a palette item id) to the palette item’s cssVar, emits var(--that-cssVar) into the semantic slot.

clearAppliedStyles removes every CSS property that the cluster could have set (palette + base roles + semantic).

Cross-references

Revision History