Zudo Token Panel

Type to search...

to open search from anywhere

Secondary cluster: configure or disable

Three-state semantics: undefined (hidden), null (hard opt-out), or a ColorClusterConfig (host-supplied).

PanelConfig.secondaryColorCluster is a three-state field. The state you pick controls both what the Color tab renders and which apply / clear / load code paths run. This recipe covers the three cases with concrete snippets.

For the underlying contract, see Color cluster reference (multi-cluster section) and the resolveSecondaryColorCluster helper.

The three states

ValueRenderApply / clear / load
undefined (field omitted)Secondary section hidden.No write. The persist envelope’s secondary slice stays inert.
nullSecondary section hidden — same as undefined visually.Hard opt-out: every secondary code path skips. The persist envelope’s secondary slice is not hydrated even if it exists in localStorage.
ColorClusterConfigSecondary palette + semantic table render below the primary.Same write semantics as the primary, scoped to the secondary palette + names.

💡 Tip

Why null is not the same as undefined. Both hide the section, but only null skips the secondary load path. A host that previously shipped a secondary cluster and now wants to permanently retire it should set secondaryColorCluster: null so old saved states do not silently re-apply on next page load.

Case 1 — secondary hidden (default)

Just omit the field. This is the recommended starting point for every new host.

// src/lib/my-panel-config.ts
import type { PanelConfig } from '@takazudo/zudo-design-token-panel';
import { primaryCluster } from './primary-cluster';

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',
  tokens: {
    spacing: [],
    typography: [],
    size: [],
    color: [],
  },
  colorCluster: primaryCluster,
  // secondaryColorCluster intentionally omitted.
};

Case 2 — explicit opt-out

Use null after a previous deploy carried a secondary cluster, and you want to make sure no user’s saved state will resurrect it on next load.

export const myPanelConfig: PanelConfig = {
  // ...common fields...
  colorCluster: primaryCluster,
  secondaryColorCluster: null,
};

⚠️ Warning

The persisted envelope under ${storagePrefix}-state-v2 may still contain a secondary slice from earlier versions. With secondaryColorCluster: null the package skips that slice on load, so users do not see stale secondary overrides. The slice itself is preserved in storage for round-trip safety — flipping back to a real ColorClusterConfig later will pick it up again unless you also bump storagePrefix or schemaId.

Case 3 — host-supplied secondary cluster

The secondary cluster has the same shape as the primary (<code>ColorClusterConfig</code>). Use a different paletteCssVarTemplate and different baseRoles / semanticCssNames so the writes do not collide with the primary cluster.

import type {
  ColorClusterConfig,
  ColorScheme,
  PanelConfig,
} from '@takazudo/zudo-design-token-panel';
import { primaryCluster } from './primary-cluster';

const accentDark: ColorScheme = {
  background: 0,
  foreground: 7,
  cursor: 7,
  selectionBg: 8,
  selectionFg: 0,
  palette: [
    '#202124', '#ff7597', '#7cd992', '#f5d97a',
    '#7ab0f5', '#c79df0', '#7fdacf', '#dde2ec',
    '#3a3d44', '#ff7597', '#7cd992', '#f5d97a',
    '#7ab0f5', '#c79df0', '#7fdacf', '#bfc6d4',
  ],
  shikiTheme: 'github-dark',
};

const secondaryCluster: ColorClusterConfig = {
  id: 'myapp-accent',
  label: 'Accent surface',
  paletteSize: 16,
  paletteCssVarTemplate: '--myapp-accent-p{n}',
  baseRoles: {
    background: '--myapp-accent-bg',
    foreground: '--myapp-accent-fg',
  },
  baseDefaults: {
    background: 0,
    foreground: 7,
  },
  semanticDefaults: {
    surface: 0,
    onSurface: 7,
  },
  semanticCssNames: {
    surface: '--myapp-accent-surface',
    onSurface: '--myapp-accent-on-surface',
  },
  defaultShikiTheme: 'github-dark',
  colorSchemes: {
    'Accent Dark': accentDark,
  },
  panelSettings: {
    colorScheme: 'Accent Dark',
    colorMode: false,
  },
};

export const myPanelConfig: PanelConfig = {
  // ...common fields...
  colorCluster: primaryCluster,
  secondaryColorCluster: secondaryCluster,
};

Case 4 — light/dark pairing on the secondary

Same shape, but with the panelSettings.colorMode light/dark pairing enabled. The panel watches document.documentElement[data-theme] and swaps schemes accordingly on init.

const secondaryCluster: ColorClusterConfig = {
  // ...same fields as Case 3...
  colorSchemes: {
    'Accent Light': accentLight,
    'Accent Dark': accentDark,
  },
  panelSettings: {
    colorScheme: 'Accent Dark',
    colorMode: {
      defaultMode: 'dark',
      lightScheme: 'Accent Light',
      darkScheme: 'Accent Dark',
    },
  },
};

ℹ️ Info

colorMode is independent on the primary and the secondary. You can set it on one and leave the other as false; the panel resolves them separately through resolveSecondaryColorCluster.

Revision History