The Design Token Panel is an in-page editor for every design token the theme ships with. It replaces the old single-purpose Color Tweak Panel with a tabbed UI covering the **Palette**, **Color**, **Font**, **Spacing**, and **Size** token families, and adds a unified JSON export/import workflow so you can round-trip a whole design through an AI assistant and back.

## Enabling the Panel

Set `designTokenPanel` to `true` in `zfb.config.ts`:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    designTokenPanel: true,
    // ...
  }),
);
```

When enabled, a palette icon appears in the header (to the left of the search icon). Clicking it toggles the panel open and closed; `Escape` closes it again from anywhere on the page.

[Dynamic content unavailable in text export]

The config field is `designTokenPanel` — set it to `true` to enable the panel. No host config file is required: the panel uses the package-default builder (`@takazudo/zudo-doc/design-token-panel-config`), derived from the shipped token manifest and the bundled color schemes, unless you point `designTokenPanelConfigModule` at your own (see [Customizing the panel](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#enabling-the-panel-customizing-the-panel) below).



### Customizing the panel

By default `designTokenPanel: true` needs no host config file — the injected panel uses the package-default builder (`@takazudo/zudo-doc/design-token-panel-config`), which derives the Palette/Color tabs from the bundled color schemes and the Font/Spacing/Size tabs from the shipped token manifest.

To ship a fully custom panel (your own token manifest, your own color schemes), write a module exporting a named `buildDesignTokenPanelConfig(mode: "light" | "dark")` builder and point `designTokenPanelConfigModule` at it — a project-root-relative path, mirroring the `chromeBindingsModule` contract:

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    designTokenPanel: true,
    designTokenPanelConfigModule: "./src/config/design-token-panel-config.ts",
  }),
);
```

This showcase uses the package-owned bootstrap and package-default builder. Set `designTokenPanelConfigModule` only when the auto-injected bootstrap should load a custom mode-scoped builder instead.

Note that the setting applies only to pages rendered through the package-injected routes: while it is set, a doc page rendered by a self-contained host `pages/` stub mounts no panel island (and no header panel button) at all, so the stub page cannot hijack the once-per-session panel config with the package-default builder. To also get a panel on stub-rendered pages, expose your client bootstrap through a fixed-target server boundary in `chromeBindings.DesignTokenPanelBootstrap` — that boundary wins everywhere (see [Host chrome bindings](https://zudo-doc.takazudomodular.com/docs/reference/host-chrome-bindings)).

## The Tab Strip

zudo-doc's panel config supplies five token tabs — **Palette**, **Color**, **Font**, **Spacing**, and **Size**. `@takazudo/zdtp` then prepends one of its own, **Inspect**, which is synthetic: it never appears in the host config and cannot be removed from it. So the strip you see always carries one tab more than the config declares, and the panel opens on **Palette**.

All five configured ids are *reserved* upstream, which means each one gets a purpose-built editor rather than zdtp's generic tier renderer. Tabs expose nothing but the tokens they own — you never have to scroll past colors to reach spacing.

### Inspect

zdtp's own tab, present whether or not a host asks for it. Arm it from the panel header (or press `I`) and then click an element in the page; the tab reports that element's computed values next to the tokens they resolve from, including inherited ones. Nothing in `zfb.config.ts` configures it — it belongs to the panel, not to this project's token manifest.

### Palette

The Palette tab surfaces the three shared OKLCH ramps that every color scheme is built from (see [Color](https://zudo-doc.takazudomodular.com/docs/reference/color)):

- **Base** — 5 stops (`--palette-base-0` … `--palette-base-4`), index 0 = lightest
- **Accent** — 3 stops (`--palette-accent-0` … `--palette-accent-2`)
- **State** — 4 named roles: `danger`, `success`, `warning`, `info` (`--palette-state-{role}`)

An **Edit** / **Check** toggle at the top of the tab switches between two views of those same ramps:

- **Edit** renders each stop as a swatch on zdtp's native OKLCH lightness/chroma/hue curve editor. Dragging a stop updates every semantic role built from it live — there is no separate "Scheme…" preset dropdown; the ramps themselves are the editable source of truth.
- **Check** turns the ramps into a contrast reader: choose a background stop and the view reports the candidate foregrounds against it, so a curve edit can be sanity-checked without leaving the panel.

See [Color Scheme Preview](https://zudo-doc.takazudomodular.com/docs/guides/color-scheme-preview) for a walkthrough.

### Color

The Color tab is a **semantic→ramp editor**. Its rows fall into three groups — the 4 base roles (`bg`, `fg`, `selection-bg`, `selection-fg`), the `--zd-*` semantic roles, and the syntax-highlighting roles (`syntaxComment`, `syntaxKeyword`, `syntaxString`, …) — and every row is wired either to a stop on the Palette tab's ramps or, for a handful of roles, to a literal OKLCH override. The tab carries no ramp of its own; it references the Palette tab's **Base** / **Accent** / **State** tiers, so editing a ramp stop there recolors every row that points at it.

#### Ref rows — grouped ramp dropdowns

Most rows render as a `<select>` with **Base** / **Accent** / **State** `<optgroup>`s, one option per stop on the Palette tab's ramps, each labelled with the stop's CSS variable and its resolved color. The row itself carries a resolved-color chip, so you can see what the reference currently evaluates to without opening the dropdown.

Picking an option wires the row's `--zd-*` custom property to `var(--palette-{tier}-{n})` — a live CSS variable reference, not a resolved color — so dragging the referenced stop's L/C/H curve on the Palette tab updates the page immediately, with no need to re-pick anything on the Color tab.

Every ref row also carries a **"Literal…"** entry at the bottom of its dropdown, for detaching the row from the ramp and pinning a one-off color instead.

#### Literal rows — per-mode AA-tuned overrides

A handful of roles can't be satisfied by a shared ramp stop in both modes (see [Per-mode literal overrides](https://zudo-doc.takazudomodular.com/docs/reference/color#per-mode-literal-overrides)) and render as literal rows by default: a `Literal…`-selected `<select>` plus an editable OKLCH swatch. Which roles start as literal is scheme-defined — in the bundled schemes those are the AA-tuned state colors (`danger` in both modes; `accentHover`, `success`, `warning`, and `info` in Default Light), the two matched-keyword tokens, the two diff syntax tokens, and any syntax role that inherits from a semantic role which is itself literal.

A literal row's swatch has a **Per-mode** checkbox. Turning it on reveals two independent OKLCH fields (Light / Dark) instead of one; the applied value becomes `light-dark(<light-value>, <dark-value>)` and the panel sets `color-scheme: light dark` on the applied root so the browser resolves the correct arm for the active mode.

#### Matched-keyword tokens

Two semantic color tokens drive how matched keywords are highlighted, rendered as literal rows (shared between light and dark — see [Per-mode literal overrides](https://zudo-doc.takazudomodular.com/docs/reference/color#per-mode-literal-overrides)) and consumed by the search widget at `pages/lib/_search-widget.tsx` to style hits in the search UI.

- `matchedKeywordBg` — background color painted behind a matched keyword.
- `matchedKeywordFg` — foreground (text) color of the matched keyword on top of that background.

#### Mode-scoped editing model

The Color tab always edits the wiring for whichever mode — light or dark — is currently active, seeded from that mode's `ColorScheme.map` in the package's `@takazudo/zudo-doc/color-schemes-defaults` (or your own `colorSchemes` override, if set — see [Color](https://zudo-doc.takazudomodular.com/docs/reference/color)).

- **A resolved light/dark appearance change re-seeds the tab's ref/literal defaults** to the new mode's wiring (e.g. `bg` flips `base-0` ↔ `base-4`) — this includes an OS change while System is selected. See [Implementation](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#implementation) for the destroy+reconfigure mechanism behind this.
- **Palette, Spacing, Font, and Size tweaks carry over appearance changes untouched.** Those tabs are mode-independent, so a ramp-curve edit or a spacing change survives light↔dark exactly as before.
- **A saved Color-tab override is scheme- and mode-scoped.** The package-default builder pairs `Default Light` and `Default Dark` in `panelSettings.colorMode`, so a saved Color override is restored only for its active scheme identity. An override made in one mode does not repaint the other; the Palette, Spacing, Font, and Size tabs remain shared across appearance changes.

[Dynamic content unavailable in text export]

**zudo-doc's builder does not declare per-mode literal defaults.** `TierItem.default` is a single string, so an AA-tuned per-mode literal (like `danger`) is seeded from whichever mode is active at reconfigure time rather than declared as a `{light, dark}` pair up front. zdtp itself no longer forces this: since 0.4.5 a color tab's `colorExtras.semanticDefaults` map accepts a `{ literal: { light, dark } }` value per semantic row, and it is the sanctioned way to ship such a default. The package-default builder simply does not populate that map yet. Runtime per-mode editing (the **Per-mode** checkbox above) is unaffected — this only limits what the *config* declares as a starting default.



### Font

Five tiers, in the order the tab renders them:

- **Scale** — the raw `--text-scale-*` steps (`2xs` → `2xl`), edited as numeric values with CSS-value sanitization.
- **Font size** — the semantic roles (`text-micro` … `text-display`). This is a *reference* tier: each row picks a **Scale** step rather than a raw value, so editing a step propagates to every role pointing at it, exactly mirroring how the Color tab's semantic rows point at ramp stops.
- **Line height**, **Font weight**, **Font family** — the remaining typography tokens. The family fields accept any valid CSS `font-family` value, including multi-stack fallbacks; invalid values (unbalanced quotes, raw JavaScript, etc.) are rejected on change so pasted snippets cannot break the page.

Tiers render with live previews — type samples, line-height, weight, and family — and the tab can push a full **specimen** into the host page itself: a read-only block set in the site's real fonts, which temporarily forces the panel into its right dock for as long as it is showing.

### Spacing

Four tiers: **Horizontal spacing** (`hsp-*`), **Vertical spacing** (`vsp-*`), **Icons** (`icon-xs` → `icon-lg`), and **Layout**. Each editable step has a numeric control with a preview bar that shrinks and grows with the value, and the tab's bulk numeric actions — multiply, add, round to step, set all — apply to a selected range of rows in one go.

Three Layout entries are surfaced read-only: `spacing-0` (a structural zero that utilities rely on), `spacing-px` (the 1px hairline), and `sidebar-w` (a responsive `clamp()` expression no single control can express). They are visible so you know they exist, not so you can move them.

### Size

Two tiers — and neither of them is icon sizing, which lives on the Spacing tab:

- **Radius** — `--radius-DEFAULT`, `--radius-lg`, and `--radius-full`. The last carries a pill toggle that switches between its `9999px` sentinel and an editable value.
- **Transition** — the default transition duration.

## Panel Chrome

Beyond the tabs, the panel shell itself carries a handful of affordances worth knowing about before you go looking for them:

- **Dock modes.** Float, right dock, bottom dock, and a collapsed mini pill, switchable with `Alt+1` … `Alt+4`. The two docks reserve space in the host document and release it when the panel closes, so the page reflows beside the panel instead of disappearing behind it. The chosen mode persists across page loads.
- **Ghost when idle.** Fades the panel while you are not interacting with it, so a color change can be judged against an unobstructed page.
- **History rail with A/B snapshots.** Undo/redo across edits, plus two slots you can save a whole token state into and flip between.
- **Token search and command palette.** Fuzzy search across every tab with per-tab hit counts, and a `Cmd/Ctrl+K` palette for jumping straight to a token.
- **Changed-token state.** Edited rows are marked, each tab shows a badge, and the footer summarises the total ("*N* tokens changed across *M* tabs"). **Changed only** filters the view down to those rows, **Copy diff** puts them on the clipboard, and **Revert all** puts every one of them back. The Font, Spacing, and Size tabs each carry their own **Reset** action as well.
- **Chain popover.** Per token, the resolution chain — what it references, what references it, and how many elements on the current page actually use it.

## Keyboard Accessibility

The tab strip implements the standard WAI-ARIA tabs pattern, and the shell adds a few panel-level shortcuts:

| Key | Action |
|---|---|
| `←` / `→` | Move focus to the previous / next tab |
| `Home` | Focus the first tab |
| `End` | Focus the last tab |
| `Enter` / `Space` | Activate the focused tab |
| `Escape` | Close the panel |
| `Alt+1` … `Alt+4` | Switch to float / right dock / bottom dock / mini pill |
| `Cmd/Ctrl+K` | Open the command palette |
| `I` | Arm element inspect, then click an element |

Tabs use automatic activation — moving focus with the arrow keys also switches the panel. Within each tab, all controls are reachable via `Tab`, and every one exposes a visible focus ring.

## Why Apply is disabled here

The panel carries an **Apply** button that writes the current diff back to source files over an HTTP endpoint. It is deliberately inert in this showcase: zdtp gates Apply on both an `applyEndpoint` and a non-empty `applyRouting` map, and zudo-doc's package-default builder configures neither, so the button stays disabled with the tooltip *"Apply unavailable: configure an apply endpoint and a non-empty routing map."* The supported round-trip here is the JSON export/import workflow below.

## JSON Export — diff-only by default

Click **Export** in the panel header to open the export modal. The modal shows a single JSON document describing the current state of every tab, wrapped in a `tabs` object keyed by tab id.

By default the export is a **diff against the defaults** — only tokens you have actually changed are included. Untouched tabs are omitted entirely, and within a changed tab only the individual tokens you edited are kept. The result is small, easy to diff, and stable enough to paste into a commit.

```json
{
  "$schema": "zudo-design-tokens/v3",
  "exportedAt": "2026-07-05T12:00:00.000Z",
  "tabs": {
    "palette": {
      "raw": {
        "--palette-accent-1": "oklch(0.700 0.158 62)"
      }
    },
    "color": {
      "semantic": {
        "--zd-bg": { "ref": { "tab": "palette", "tier": "base", "item": "base-3" } },
        "--zd-danger": {
          "literal": { "light": "oklch(.505 .170 25)", "dark": "oklch(.655 .170 25)" }
        }
      }
    },
    "spacing": {
      "raw": { "--spacing-hsp-md": "1.25rem" }
    }
  }
}
```

`$schema` is `zudo-design-tokens/v2` while every changed value is a plain string or palette-index number, and upgrades automatically to `/v3` the moment any Color-tab row holds a `{ref}` or `{literal}` object — which, under the mode-scoped model, is normal for a real zudo-doc export. `ref` points at a Palette-tab ramp stop (`{tab, tier, item}`); `literal` is either a single OKLCH string or, for a **Per-mode** row, a `{light, dark}` pair. Re-importing via **Load** round-trips both shapes cleanly.

If you import a Color-tab `literal` as a single `light-dark(...)` string through **Load from JSON…**, opening its swatch shows an expression disclosure, with the hex input and color sliders disabled. Choose **Edit as literal** to enable the controls; editing replaces the expression with a literal color. This single-string import differs from the **Per-mode** `{light, dark}` pair described above.

Flip the **Show defaults too** toggle in the modal header to emit the full token tree (every tab, every token, including untouched defaults). Use this form when you want a complete snapshot to hand to a designer or AI that does not know your theme.

## Load-from-JSON

The **Load** button opens the import modal. Paste a JSON document — either from an earlier export or one produced by an AI — and the panel:

1. Validates the shape (version, known keys, well-formed values).
2. Reports any unknown keys or malformed values as non-fatal warnings.
3. Merges the document over the current defaults. Missing tokens keep their defaults, explicit tokens override them.
4. Persists the result to `localStorage` and re-applies every CSS custom property immediately.

Invalid JSON is rejected with a specific error message; partial imports (e.g. a JSON that only changes `spacing.hsp.md`) are a first-class use case.

## AI Workflow

The diff-only export and Load-from-JSON together enable a simple round-trip with any chat-based AI:

1. **Tweak** anything you want to hand off — or leave the panel untouched.
2. **Export** the diff and copy the JSON.
3. **Paste** the JSON into an AI chat with a natural-language request, for example:

   > Here is my current design token diff. Rework this into a warmer, higher-contrast typographic scale while keeping the color palette untouched, and return only the JSON.

4. **Paste** the AI's response into **Load**.
5. The panel validates, merges, and applies the new tokens instantly. Hit **Revert all** in the changed-token toolbar to roll everything back if you don't like the result.

Because the default export is a diff, the AI only sees the tokens you care about — it doesn't have to reason around dozens of unchanged defaults. Turn on **Show defaults too** only when the AI needs the complete picture (for example, producing a new theme from scratch).

## Persistence

Everything the panel remembers hangs off one storage prefix, `zudo-doc-tweak`, fixed by the package-default builder. Token state itself lives in a single versioned envelope; the rest is panel chrome and per-feature state.

| Key | Holds |
|---|---|
| `zudo-doc-tweak-state-v4` | The unified token envelope — every tab's state side by side. Color state is keyed by active scheme identity, so light and dark tweaks restore independently. Older `-state-v3`, `-state-v2`, and `-state` envelopes are still read and migrated forward. |
| `zudo-doc-tweak-open` | Whether the panel was left open |
| `zudo-doc-tweak-position`, `-size` | Floating shell position and dimensions |
| `zudo-doc-tweak-dock`, `-dock-size` | Dock mode (`float` / `right` / `bottom` / `mini`) and dock dimensions |
| `zudo-doc-tweak-spawn-ordinal` | Panel cascade position, persisted across body swaps |
| `zudo-doc-tweak-density` | Tab-grid density preference |
| `zudo-doc-tweak-ghost` | Ghost-when-idle preference |
| `zudo-doc-tweak-specimen`, `-on-page-specimen` | Font specimen toolbar settings, and whether the specimen is rendered into the host page |
| `zudo-doc-tweak-snapshot-a`, `-snapshot-b` | The two A/B snapshots |
| `zudo-doc-tweak-last-applied` | Apply comparison baseline (unused here — Apply is disabled) |
| `zudo-doc-tweak:visible`, `zudo-doc-tweak:autoload` | Lazy-load visibility intent and the owner-mode autoload flag |
| `zudo-doc-tweak-elpath-enabled`, `-domtweaker-enabled` | Element-path picker and DOM Tweaker enabled bits |
| `zudo-doc-tweak-highlight-slots`, `-highlight-outline-width` | Token-highlight slot colors and outline width |
| `zudo-doc-tweak-highlight-active` | Active highlight map — the one entry kept in `sessionStorage` rather than `localStorage` |

`@takazudo/zdtp` owns this list and may extend it in a future release; the guarantee zudo-doc makes is about the prefix, not about the individual keys.

**Revert all** clears every changed token across every tab, and the Font, Spacing, and Size tabs each have a **Reset** action of their own; both restore the configured defaults. The installed `@takazudo/zdtp` package owns the storage format and lifecycle; zudo-doc does not parse or rewrite persisted envelopes itself.

Changing the resolved light/dark appearance does **not** delete your saved tweaks, and an open panel stays open across the change. The appearance menu dispatches `color-scheme-changed` only when the resolved appearance changes; the host destroys and reconfigures the panel for the new mode (see [Implementation](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#implementation)) rather than patching it in place, so the Color tab's ref/literal *defaults* follow the newly active mode and any saved Color override is restored for that mode's scheme identity. Choosing System while the device already matches the current appearance changes the saved preference without rebuilding the panel. Palette, Spacing, Font, and Size tweaks carry over untouched. See [Mode-scoped editing model](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#the-tab-strip-color-mode-scoped-editing-model).

[Dynamic content unavailable in text export]

The storage prefix `zudo-doc-tweak` is locked in the package-default builder (`@takazudo/zudo-doc/design-token-panel-config`, which this showcase consumes directly) and will not change — so tweaks saved by an earlier release carry over untouched. Persistence details beyond that prefix belong to `@takazudo/zdtp`, not to a repository-owned zudo-doc compatibility layer.



## Runtime dependencies and CSS

The installed public `@takazudo/zdtp` 0.8.6 artifact carries `preact: ^10.29.1` as its own dependency (through 0.8.5 it was a peer that the project had to install). Installing zdtp is enough when enabling the panel or bundling it for a custom mount; zudo-doc's own components use zudo-react and do not require Preact nodes. The panel owns its separate Preact subtree and Tailwind browser engine, which does not register site utilities.

```sh
pnpm add @takazudo/zdtp@0.8.6
```

```css
@import "@takazudo/zdtp/styles.css";
```

`./styles.css` and `./styles` are public exports for the same shipped CSS file. Keep the import with the other leading imports; do not import a physical `dist/` path. The scaffold adds it only when the panel feature is selected.

## Implementation

The panel UI is provided by the `@takazudo/zdtp` (zdtp) npm package. The package-owned `DesignTokenPanelBootstrap` island and its wiring mechanism — calling `configurePanel`, hooking zfb's navigation lifecycle, and the mode-scoped destroy+reconfigure described below — live in `@takazudo/zudo-doc/design-token-panel-bootstrap`. Its fixed-target server boundary mounts the client bootstrap, which calls `runDesignTokenPanelBootstrapOnce(buildDesignTokenPanelConfig, "default", scope.abortSignal)` inside `scope.onActivate`. Here, `buildDesignTokenPanelConfig` is a `(mode) => PanelConfig` builder — this is what lets the Color tab follow light/dark. The panel self-mounts as a side effect once the island hydrates.

### Lazy loading

`@takazudo/zdtp` is not part of the initial page bundle. `bootstrapDesignTokenPanel` only registers a small interim click listener at mount; the actual dynamic import of zdtp — and the `configurePanel` call that follows it — happens the first time the header trigger dispatches `toggle-design-token-panel`. Clicks that land before the import resolves are queued and coalesced into a single net show/hide once configure finishes, so a rapid double-click never fires it twice.

The bootstrap reaches zdtp only through the package-owned `@takazudo/zudo-doc/zdtp-loader` subpath. The preset installs a throwing stub for that one specifier whenever zdtp is not bundled — resolved as `bundleZdtp ?? designTokenPanel` — so a project that never sets `bundleZdtp` keeps exactly today's behavior: panel off means no zdtp chunks at all. Setting `bundleZdtp: true` keeps the real loader even with the panel off; see [Mounting your own panel](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#implementation-mounting-your-own-panel) for why you'd want that. Your own `import "@takazudo/zdtp"` statements are not affected either way.

A returning visitor with saved state doesn't wait for that click. On mount, the bootstrap probes `localStorage` under the active (pack-scoped) storage prefix and loads zdtp immediately — before any interaction — when any of the following is true:

- the panel was left open (`${prefix}-open` is `"1"`),
- a token override was saved (any `${prefix}-state*` key exists), or
- an owner-mode flag is set (`${prefix}:autoload`, `${prefix}-elpath-enabled`, or `${prefix}-domtweaker-enabled`).

The `${prefix}-open` and owner-mode checks are exact-key lookups; only the `${prefix}-state*` check walks every key currently in `localStorage` (there is no native prefix-query API), but it only ever treats a key as a hit when it starts with the exact active prefix's stem — so it can't false-positive on an unrelated app's own `*-state` keys, even though it does look at every key name to find one. Switching to a theme pack whose namespace holds saved state triggers the same eager load.

Until zdtp has loaded, by either path, `window.zdtp` (the console API zdtp installs once it configures) does not exist. Once loaded it stays loaded — its own listener takes over the toggle event directly — for the rest of the page's life; only a full page load returns to this lazy state.

On every `color-scheme-changed` event (dispatched when the resolved appearance changes, including an OS change while System is selected), the bootstrap reads the new mode and coalesces rapid changes onto a single macrotask, then calls `handle.destroy()` followed by `configurePanel(buildDesignTokenPanelConfig(mode))` — zdtp's `configurePanel` throws if re-run in place with a structurally different config, so destroy-then-reconfigure is the only supported way to swap the Color tab's mode-scoped defaults. A preference change that leaves the resolved appearance unchanged uses `theme-preference-changed` and does not rebuild the panel. If the panel was open, it's re-mounted open.

The header trigger button dispatches the `toggle-design-token-panel` custom event on `window`. zdtp listens for this event natively, so no additional wiring is needed when the trigger is customised through `settings.headerRightItems`.

### Mounting your own panel

`designTokenPanel` and `bundleZdtp` control two different things, and conflating them is exactly what caused zudo-doc#4261: `designTokenPanel` controls **mounting** — whether the package's own `DesignTokenPanelBootstrap` island renders; `bundleZdtp` controls **bundling** — whether `@takazudo/zdtp` ships in the build at all. They default to tracking each other (see [Lazy loading](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#implementation-lazy-loading)), but a host that wants to mount its own panel island — instead of the package's — needs to set them independently.

Call the same bootstrap the package island calls, from your own `"use client"` island — and call it through `runDesignTokenPanelBootstrapOnce`, not `bootstrapDesignTokenPanel` directly. The once-latch is what keeps a re-render or a soft navigation from bootstrapping a second time; a second bootstrap binds a second `toggle-design-token-panel` listener, so one trigger click counts as two toggle intents and the panel never opens.

```tsx
"use client";

import { getScope } from "@takazudo/zfb/zudo-react";
import { runDesignTokenPanelBootstrapOnce } from "@takazudo/zudo-doc/design-token-panel-bootstrap";
import { buildDesignTokenPanelConfig } from "@/config/design-token-panel-config";

export function HostPanelBootstrap() {
  const scope = getScope();
  scope.onActivate(() => {
    runDesignTokenPanelBootstrapOnce(buildDesignTokenPanelConfig, "configured", scope.abortSignal);
  });
  return null;
}
```

Wire a server component into `BodyEndIslands` that returns a Fragment containing `Island({ when: "load", children: <HostPanelBootstrap /> })`, using a static import of `HostPanelBootstrap`. The client bootstrap activates after hydration; it does not configure the panel during SSR. Keep the Fragment around the Island result, including when passing the server component through a binding object.

Two settings move together to make this work:

- `designTokenPanel: false` — so the package mounts nothing of its own; only your island renders a panel.
- `bundleZdtp: true` — so the loader stays real. Leave it unset with the panel off and `@takazudo/zudo-doc/zdtp-loader` shadows to a throwing stub; your bootstrap's dynamic import rejects the moment the panel is opened, even though the build itself went green.

You also own the trigger. `designTokenPanel: false` drops the built-in `design-token-panel` trigger from `settings.headerRightItems` (the package refuses to render a button that would dispatch into a document with no panel island of its own), so a host-mounted panel needs its own button — a `headerRightItems` entry resolved through `headerRightComponents`, or any element of yours — dispatching `toggle-design-token-panel` on `window`.

You also install `@takazudo/zdtp` yourself (it's an optional peer dependency of `@takazudo/zudo-doc`) and import its stylesheet — `@import "@takazudo/zdtp/styles.css"` — in your own `global.css`. Nothing adds that line for you here: `create-zudo-doc` writes it only when you scaffold a project with `designTokenPanel` on.

A missing `@takazudo/zdtp` with `bundleZdtp: true` fails the build resolving `@takazudo/zudo-doc/zdtp-loader`'s static re-export — loud, by design, rather than a silent runtime failure.

[Dynamic content unavailable in text export]

`designTokenPanel: true` together with `bundleZdtp: false` is rejected at config resolution: a mounted package panel with a stubbed loader is guaranteed to throw the moment it's opened, so zudo-doc fails the build instead of shipping it.



## Settings Reference

| Setting | Type | Description |
|---|---|---|
| `designTokenPanel` | `boolean` | Mounts the package's own panel island. Default `false`. |
| `bundleZdtp` | `boolean` | Bundles `@takazudo/zdtp` into the build. Default `undefined` (follows `designTokenPanel`: `bundleZdtp ?? designTokenPanel`). `true` with `designTokenPanel: false` bundles zdtp without mounting the package panel — the combination for mounting your own panel (see [Mounting your own panel](https://zudo-doc.takazudomodular.com/docs/reference/design-token-panel#implementation-mounting-your-own-panel)). `false` with `designTokenPanel: true` is rejected at config resolution. |
| `designTokenPanelConfigModule` | `string` | Project-root-relative path to a module exporting `buildDesignTokenPanelConfig(mode)` for a fully custom panel. Default `undefined` (package-default builder is used). |

The default tokens themselves — spacing scale, font scale, icon sizes, and palette aliases — ship in `@takazudo/zudo-doc/theme.css`. A scaffold's later `src/styles/global.css` `:root` block is an override point; for color ramps and mappings, use the package's `@takazudo/zudo-doc/color-schemes-defaults` or your own `colorSchemes` override (see [Color](https://zudo-doc.takazudomodular.com/docs/reference/color)). The panel only tweaks the in-browser copy of those tokens; it never writes to source files.

[Dynamic content unavailable in text export]

When you've found a combination you like, export the diff and paste it into a commit message or design doc — it's the smallest reproducible description of the change you just made.