Theme Creation
Authors a new theme for @kong/design-tokens (a Style Dictionary package: primitive color
palette → semantic tokens → component tokens, guarded by themes.spec.mjs). The hard part isn't
the mechanics — bundled scripts handle those deterministically — it's faithfully matching a
visual identity: not just colors, but component character (button padding/radius/states, card
elevation, input shape, typography). That judgment work is where your attention goes.
The work splits cleanly, and this skill is built around the split:
- Mechanical, done by scripts — scaffolding the theme directory exhaustive-and-empty directly
from the canonical token tree (no donor theme, no placeholder colors), listing the live token
set, rendering a real-component preview, tracking what's still unfilled, tearing down. You don't
hand-do these, so they can't be forgotten or done half-way.
- Judgment, done by you (with the
frontend-design skill) — perceiving the source, deriving a
cohesive palette + component treatments with taste, and confirming the rendered result matches.
All paths assume the working directory is packages/design-tokens/ unless noted. The scaffold,
preview, and unfilled-report commands (pnpm theme:scaffold, pnpm theme:preview --,
pnpm themes:unfilled) are what this flow uses. theme:preview is a package.json convenience
alias for the skill's own scripts/preview.mjs — the script lives in and is owned by this skill,
not the package, so it stays portable if the skill is ever used standalone.
pnpm themes:sync exists too, but it's a repo-maintenance command for reconciling existing
themes when a token is added to tokens/source/**/tokens/components/** — out of scope for
creating one new theme (see the Scope guardrail below), so this flow never calls it.
Scope guardrail
This skill creates exactly one new theme and touches only its two co-located files:
themes/<new>/<new>.theme.json and themes/<new>/<new>.alias.color.json. That's the whole
footprint — themes.spec.mjs no longer needs editing, because it treats every theme in themes/
as exhaustive by default (only classic-day/classic-night are opted out as semantic-only), so a
new theme is covered by the guards automatically. theme:scaffold makes exactly these two files;
theme:scaffold <name> --teardown reverts them. Every existing theme/palette file is a read-only
reference. Nothing else is in scope — not themes.spec.mjs, README.md, docs/**, config.mjs,
_manifest.alias.color.json, other packages — even if it looks helpful; flag it, don't fix it.
Generated dist/themes/<new>.* output is expected, not a violation. Before finishing, git status
in the repo root: the only source changes should be the two theme files (in-repo path), or nothing
at all (standalone path, after teardown).
Step 0 — Read first
Skim these before building a mental model from memory — the guards are the real spec:
packages/design-tokens/README.md §"Token Tiers", §"Themes", §"Creating a new theme"
references/token-model.md — condensed tier model + guard behavior
references/component-tokens.md — how to theme component visual identity with taste (read
this whenever the goal is matching a real look, not just recoloring)
references/design-inputs.md — how to work from a screenshot, URL, ported theme, or verbal brief
references/authoring-checklist.md — linear checklist + known repo-doc discrepancies
references/minimal-overrides.md — read instead of Steps 3–6 when the user wants only a
handful of tokens changed, not a full theme
Step 1 — Ask: in-repo or standalone?
Don't guess:
- In-repo — the theme's files are committed into
packages/design-tokens/, shipping as part
of the package (a new officially-supported theme).
- Standalone — the user just wants a
dist/themes/<name>.css file for some other app;
nothing survives in this repo (scaffold now, tear down at the end).
If unclear, ask: "Committed into the design-tokens repo, or a standalone CSS file for another app?"
Step 2 — Validate the name
Kebab-case (acme-day, not AcmeDay/acme_day), and not already present in themes/. The
scaffold command enforces both and errors clearly if either fails — but check up front so you don't
walk the user through Step 2.5's design brief before discovering the name is taken.
Step 2.5 — Gather the design brief
Get concrete design intent before scaffolding — a vague sense produces a "close enough" theme that
misses the request. Establish (from what the user said, or by asking):
- Light, dark, or a day/night pair, or a single standalone theme — these are independent
themes; there is no structural coupling between a "day" and "night" name. A day/night pair is
just two full themes you choose to author together and (usually, not necessarily) share a
palette between. Also establish full theme vs. narrow override (a handful of tokens → use
references/minimal-overrides.md instead of this flow).
- The source of the look — exact brand colors, a screenshot/mockup, a URL to emulate, an
existing published theme to port (VS Code/editor/terminal — fetch its real source, never
reconstruct from memory), or verbal direction ("dark, moody, high-contrast"). Any of these is
real direction.
references/design-inputs.md covers how to work each source (inspecting screenshots, pulling
real getComputedStyle values from a URL, porting a VS Code theme, when to lean on the
frontend-design skill). Read it now if the brief is anything beyond an exact hex code. Don't
re-ask for what the user already gave — confirm your reading of it.
Step 3 — Scaffold (deterministic)
Run the scaffold command. It generates the new theme directly from the canonical token tree —
no donor theme copied, no #FF00FF placeholder:
- every semantic token (
tokens/source/**) is seeded with its real default value — a safe,
sensible starting point;
- every component token (
tokens/components/**) is seeded as an empty slot for you to fill
deliberately — a component token's value is a genuine design decision (e.g. an alert's danger
background is a light tint, not the strong semantic danger color) and can't be safely defaulted;
- the palette is seeded from
classic-day's real neutral values, so the theme builds, renders,
and can publish to the preview npm channel immediately.
The result is exhaustive by construction — no classification step, no template to compare against.
# from packages/design-tokens/
pnpm theme:scaffold <name>
This also prints the component-grouped token inventory (re-print anytime with
pnpm theme:scaffold <name> --inventory) — use it as the backbone of the Step 3.5 design spec.
pnpm themes:unfilled <name> reports every component slot still empty and every palette family
still identical to the seed, so nothing gets forgotten silently.
For a day/night pair: scaffold both names independently, author the day theme fully, then —
since dark mode is typically the same palette with a handful of tokens re-pointed to darker
steps (token-model.md) — copy the finished day theme's palette into the night theme's
<name>.alias.color.json as a starting point, and re-point only the tokens that should change.
This is a convenience, not a requirement: if the user wants genuinely different palettes for day
and night, author each independently. Once both are built, Step 5.5's preview can render the pair
together — Original | <day> | <night> in one page — instead of previewing each separately.
Step 3.5 — Write the design spec, and confirm it
Turn the brief + the scaffold's grouped inventory into a short structured spec, presented in
chat (never written to a repo file). This is the artifact Step 4 executes against; writing a
section per dimension forces an explicit decision — including "unchanged" — instead of relying on
remembering what matters this time. Use references/component-tokens.md as the mental model.
- Colors — always an actively-derived section: background/surface hierarchy, borders,
supporting hues, status colors, not just the one named brand color. A single hex is a
starting point for a cohesive palette, never a ceiling. (This is the "one button recolored"
failure mode — a color-only theme reads as the same UI tinted.)
- Component match (the components the source shows) — the part that most decides whether it
looks like the source, and the one that's most often gotten wrong. For each key component the
source shows (especially buttons, cards, input-type elements, badges, alerts, etc.), write a row whose
right-hand side names which Kong component + tokens reproduce it, and whose left-hand side
covers both halves of the treatment — never color alone:
- Color: fill, text, border, and the hover/active/focus/disabled states.
- Geometry (a REQUIRED field on every row, not an afterthought):
border-radius,
border-width, padding-x/padding-y, font-size, font-weight, line-height — each set
to the value you measured (getComputedStyle), or the literal word "unchanged" if the
source genuinely matches the template. A row that lists color but leaves geometry blank is an
incomplete row: a button that is the right color and radius but the wrong padding/weight/size
still doesn't read as the source (the exact miss that shipped once — color+radius done,
--kui-button-padding-*/-font-weight/-font-size-* left at Kong's compact defaults).
Map by visual equivalence, not by role name: the source's most prominent call-to-action
becomes Kong's primary button and must reproduce that button's exact treatment — even if its
color is a bright "accent-looking" one — see component-tokens.md "Match the source's components
— don't recolor by role name" for the gold-pill failure this rule prevents. Set both the component
token and its semantic fallback so it renders on whatever Kongponents version the user runs (also
in component-tokens.md).
- System propagation (every other component the source does NOT show) — a REQUIRED spec
section, not optional breadth. The scaffold inventory lists ~100 component groups; a source shows
a handful. Every remaining family must carry the same brand character you just established,
derived from what the source did let you determine — not left at Kong's defaults (a theme whose
buttons are on-brand but whose checkboxes, switches, inputs, and cards are still Kong-gray reads
as half-finished). Walk the inventory family by family and give each a derived treatment or an
explicit "neutral — unchanged"; no family left un-considered.
component-tokens.md "Propagate the
brand across the whole component system" is the how — the derivation rules (brand hue → all "on"
states, roundedness, focus rings, density) and which families to cover.
- Typography, radius, shadow, spacing — a section where the source shows a direction; mark
"unchanged from the template" explicitly where it doesn't. Inventing a type scale or radius from
nothing isn't breadth, it's fabrication — but leaving a distinctive one unmatched is the miss.
- If something the user described can't be expressed as a token value (a typewriter animation,
ASCII art — that's component code, out of scope), say so; don't fabricate a token or drop it.
Confirm the spec with the user before Step 4. A build cycle is cheap to repeat; redoing a
wrong direction across dozens of tokens is not. Lean on frontend-design for the aesthetic
calls (deriving a palette from one hex, matching a "vibe," state derivation, contrast).
Step 4 — Fill in the values
The files already exist and are complete; now set values per the confirmed spec.
themes/<new>/<new>.alias.color.json — replace the seeded classic-day values with the
theme's real palette. Keep the exact key set; set each changed $description to
"Alias for <VALUE>." exactly (this one is guard-checked). Run
pnpm themes:unfilled <name> to see which families are still unchanged from the seed — nothing
should be left unintentionally identical to classic-day's neutrals. (Skip for a night variant —
use the day theme's finished palette per Step 3.)
themes/<new>/<new>.theme.json — set values across every dimension the spec addressed:
- Color tokens (
{color.alias.*} refs): point each at the step whose role/contrast fits
this palette — the scaffolded value is tokens/source's shared default mapping, not a
decision specific to this theme; don't just keep it unexamined.
- Component match: for each component-match row in the spec, set the component tokens and
the semantic tokens they fall back to (per
component-tokens.md) so the treatment renders on
whatever Kongponents version the user runs. Set the geometry tokens alongside the color
ones — for a button that means --kui-button-padding-x/y-*, -border-radius-*,
-font-weight, -font-size-*, -line-height-*, not just the color tokens; the primary
button's fill/text/border and radius/padding/weight/size must reproduce the source's
primary CTA. The scaffold inventory grouped these by component so you can do one component at
a time. pnpm themes:unfilled <name> lists every component slot still empty — work it down to
zero (or a deliberate "neutral — unchanged" you've accounted for in the spec).
- System propagation: work the "every other component" spec section too — set the
selected/checked/active fills, focus rings, radii, and densities of the unshown component
families to the derived brand values (per
component-tokens.md "Propagate the brand across
the whole component system"). A theme whose buttons are branded but whose checkboxes/switches/
inputs/cards are still Kong-default is not finished.
- Literal tokens (radius, shadow, padding, font-family, etc.): set the ones the spec called
for. Typeface change = global find/replace of the one font-stack string across all
-font-family tokens.
- Literal colors you introduce (focus rings, overlays, shadows) must derive from the new
palette (exact palette channels — see
component-tokens.md). For a literal hex or numeric
rgb()/rgba(), the off-source-color guard rejects an off-palette value at build time — but
it does not parse color-mix(), hsl(), or CSS color keywords, so a color-mix() shadow
specifically needs the same care without that safety net (see token-model.md "Other
invariants" for exactly what the guard does and doesn't catch).
- Follow the
$description rules in token-model.md (nothing guards theme-token descriptions,
so get them right up front).
Step 5 — Build
pnpm --filter @kong/design-tokens test # pretest runs build:tokens; runs the guards
pnpm --filter @kong/design-tokens lint
A failing guard names the exact problem (missing/extra token, stale $description, off-source
color) — fix that specific thing. Note: the package sandbox (pnpm sandbox) does not render
themes; use Step 5.5's preview instead.
Step 5.5 — Preview and verify against the source
Structural pass (guards green) ≠ looks right. Close the gap with two checks:
Grep the compiled values for what mattered most in the spec — color and non-color:
grep -in -- "--kui-color-background-primary:" dist/themes/<new>.css
grep -in -- "--kui-button-border-radius-medium:" dist/themes/<new>.css # etc. per spec
Catches a token pointed at the wrong on-palette step, or a spec'd category left at the default.
Render real components under the theme and compare. This is the check that actually proves
fidelity, and it now works reliably:
pnpm theme:preview -- <name...> [--port 8747] [--kongponents <ver>]
# equivalent: node ../skills/theme-creation/scripts/preview.mjs <name...> [--port 8747] [--kongponents <ver>]
(theme:preview is a design-tokens/package.json convenience alias for the skill's own
preview.mjs — the script itself lives in and is owned by this skill, not the package, since
it's a skill-verification tool with no correctness contract worth unit-testing, not a
tokens/themes build artifact. -- is required so pnpm forwards the theme name(s) and flags
instead of consuming them itself.)
It serves a side-by-side Original vs. themed gallery of real @kong/kongponents components
(buttons in every state, badges, cards, inputs, radios/switch, multiselect, textarea, date
picker, code block, tabs, external link, filter group, a paginated table, modals/prompts/
slideouts, tooltips, and alerts) loaded from a CDN — no install. Pass more than one theme name to
compare them together — e.g. a day/night pair with
preview.mjs acme-day acme-night renders Original | acme-day | acme-night as three columns on
one page, which is the common case since a pair is usually eyeballed side by side rather than one
theme at a time. (Its own generated files land in
packages/skills/theme-creation-workspace/preview/<port>/, which is gitignored — you don't need
to clean those up.) Navigate a browser tool to the printed http://localhost:<port>/index.html,
screenshot it to a path outside the repo entirely (e.g. your scratch/tmp directory) — a
screenshot saved anywhere inside the repo tree, even a gitignored one, is still a scope leak an
agent should avoid creating — and view it next to the source. The modal, prompt, and slideout
examples render behind trigger buttons, and the tooltip examples behind hover triggers — a
single default screenshot only shows the closed triggers, so click/hover them with the browser
tool to reveal and screenshot the themed overlay surfaces too.
Preview against the Kongponents version the user actually runs. A version only consumes the
tokens it was built to read — a published version may read only the semantic fallback, so
component tokens (button radius/padding, per-appearance colors, etc.) won't show and a correct
theme looks under-changed. Observed concretely: the published latest consumes none of the
--kui-button-* component tokens — not geometry (-padding-*/-border-radius-*/-font-*) and
not per-appearance colors (-secondary/-tertiary) — it hardcodes button geometry and derives
secondary/tertiary from the primary semantic tier. So a two-tone brand or a distinct button
density cannot be verified on latest; you must preview against the build that consumes the
component tokens. Set both tiers (Step 3.5) so it renders on either, and pass
--kongponents <version|tag> (or --kongponents-css/--kongponents-esm for a PR/canary build)
to match the target — ask the user which build they run if you don't know. If the source's
component character isn't showing, first rule out version skew — don't "fix" a theme that's
already right — but don't hide behind it either: if the target version consumes the tokens and
it still doesn't match, the theme is wrong.
Acceptance bar — this is the check the whole skill exists for. Put the themed panel next to
the source and compare the key components directly, component by component: does the themed
primary button match the source's primary CTA in fill, text color, border, and geometry
(radius, padding, font-weight, font-size)? Confirm geometry by reading it back with
getComputedStyle on the themed component — a px-for-px check, not an eyeball — because a
wrong-density button is easy to miss visually at a glance. Secondary/tertiary/danger buttons?
Card surface and elevation? Inputs? Then check the propagated components the source never
showed — checkbox/radio/switch checked fills, input focus rings, selected tabs/rows — do they
carry the brand, or are they still Kong-gray? A "close enough" that leaves the most visible
component visibly unlike the source (wrong color, wrong shape, wrong density), or that leaves
whole component families un-branded, is not done, not a rendering caveat. Name each mismatch
and go back to Step 3.5/4 to fix it (re-map the role, set the missing tier, set the geometry,
propagate the brand).
Get the user's sign-off on the rendered result (the screenshots + grep), not just the planned
spec, before calling it done. If no browser tool is available, restate the confirmed values —
color and component/typography, component by component against the source — and have the user
preview the CSS themselves; don't declare victory on guards alone.
A good preview does not prove completeness. An unfilled component token compiles to nothing at
all (never --x: ; or --x: initial;), so it falls through to its semantic default exactly as
gracefully as a deliberately unbranded family would — the preview can't tell those two cases
apart. Run pnpm themes:unfilled <name> before calling it done; it's the only check that actually
distinguishes "still empty" from "intentionally at the default."
Step 6A — In-repo: done
git status should show exactly the new themes/<new>/ directory (its two files) — commit those
(no themes.spec.mjs change; it classifies the new theme as exhaustive automatically).
dist/themes/<new>.* regenerates on build; nothing to extract. No kong-konnect/portal
override is needed for a theme created by this skill — that concern applies only to changes to
classic-day/classic-night.
Step 6B — Standalone: extract, then tear down
- Iterate on Steps 4–5.5 with the user until the preview looks right.
- Extract the deliverable:
dist/themes/<new>.css (a self-contained
@layer kui.theme { [data-kui-theme="<new>"] {…} } block; also .mjs/.cjs/.d.ts if they
want the JS object). Ask where to copy it. Verify it's fully resolved — no {color.alias…},
no var(--kui-color-alias…), no : undefined;; if any appear, rebuild before handing off.
- Tear down — leave the repo exactly as it started:
pnpm theme:scaffold <name> --teardown
pnpm --filter @kong/design-tokens test # confirm green
Then git status — no lingering diff in packages/design-tokens/.
1---2name: theme-creation3description: Use this skill whenever the user wants to create, add, author, or scaffold a new theme for the @kong/design-tokens package — including phrases like "add a new theme", "create a new theme", "make a dark/light theme", "I need a new theme for Kongponents", "a new data-kui-theme", "match this screenshot/mockup", "make our theme look like this site/URL", "here are our brand colors", "port/emulate an existing theme", "make a theme like One Dark Pro", or "match this VS Code/editor/terminal theme". Also use it if the user wants to modify how an existing theme relates to design tokens (semantic vs component tokens, color aliases/palettes) as part of building a new one, or supplies a screenshot, mockup, reference URL, brand guideline, or the name of an existing published theme (editor, terminal, or otherwise) as the basis for a theme's look. Make sure to trigger this even if the user doesn't say "design-tokens", "Style Dictionary", or "theme" explicitly — a request to reskin, rebrand, or restyle the product to match so4---56# Theme Creation78Authors a new theme for `@kong/design-tokens` (a Style Dictionary package: primitive color9palette → semantic tokens → component tokens, guarded by `themes.spec.mjs`). The hard part isn't10the mechanics — bundled scripts handle those deterministically — it's **faithfully matching a11visual identity**: not just colors, but component character (button padding/radius/states, card12elevation, input shape, typography). That judgment work is where your attention goes.1314The work splits cleanly, and this skill is built around the split:15- **Mechanical, done by scripts** — scaffolding the theme directory exhaustive-and-empty directly16 from the canonical token tree (no donor theme, no placeholder colors), listing the live token17 set, rendering a real-component preview, tracking what's still unfilled, tearing down. You don't18 hand-do these, so they can't be forgotten or done half-way.19- **Judgment, done by you (with the `frontend-design` skill)** — perceiving the source, deriving a20 cohesive palette + component treatments with taste, and confirming the *rendered* result matches.2122All paths assume the working directory is `packages/design-tokens/` unless noted. The scaffold,23preview, and unfilled-report commands (`pnpm theme:scaffold`, `pnpm theme:preview --`,24`pnpm themes:unfilled`) are what this flow uses. `theme:preview` is a `package.json` convenience25alias for the skill's own `scripts/preview.mjs` — the script lives in and is owned by this skill,26not the package, so it stays portable if the skill is ever used standalone.27`pnpm themes:sync` exists too, but it's a repo-maintenance command for reconciling *existing*28themes when a token is added to `tokens/source/**`/`tokens/components/**` — out of scope for29creating one new theme (see the Scope guardrail below), so this flow never calls it.3031## Scope guardrail3233This skill creates exactly **one new theme** and touches only its **two co-located files**:34`themes/<new>/<new>.theme.json` and `themes/<new>/<new>.alias.color.json`. That's the whole35footprint — `themes.spec.mjs` no longer needs editing, because it treats every theme in `themes/`36as exhaustive by default (only `classic-day`/`classic-night` are opted out as semantic-only), so a37new theme is covered by the guards automatically. `theme:scaffold` makes exactly these two files;38`theme:scaffold <name> --teardown` reverts them. Every existing theme/palette file is a read-only39reference. Nothing else is in scope — not `themes.spec.mjs`, `README.md`, `docs/**`, `config.mjs`,40`_manifest.alias.color.json`, other packages — even if it looks helpful; flag it, don't fix it.41Generated `dist/themes/<new>.*` output is expected, not a violation. Before finishing, `git status`42in the repo root: the only source changes should be the two theme files (in-repo path), or nothing43at all (standalone path, after teardown).4445## Step 0 — Read first4647Skim these before building a mental model from memory — the guards are the real spec:48- `packages/design-tokens/README.md` §"Token Tiers", §"Themes", §"Creating a new theme"49- `references/token-model.md` — condensed tier model + guard behavior50- `references/component-tokens.md` — **how to theme component visual identity with taste** (read51 this whenever the goal is matching a real look, not just recoloring)52- `references/design-inputs.md` — how to work from a screenshot, URL, ported theme, or verbal brief53- `references/authoring-checklist.md` — linear checklist + known repo-doc discrepancies54- `references/minimal-overrides.md` — read *instead* of Steps 3–6 when the user wants only a55 handful of tokens changed, not a full theme5657## Step 1 — Ask: in-repo or standalone?5859Don't guess:601. **In-repo** — the theme's files are committed into `packages/design-tokens/`, shipping as part61 of the package (a new officially-supported theme).622. **Standalone** — the user just wants a `dist/themes/<name>.css` file for some *other* app;63 nothing survives in this repo (scaffold now, tear down at the end).6465If unclear, ask: "Committed into the design-tokens repo, or a standalone CSS file for another app?"6667## Step 2 — Validate the name6869Kebab-case (`acme-day`, not `AcmeDay`/`acme_day`), and not already present in `themes/`. The70scaffold command enforces both and errors clearly if either fails — but check up front so you don't71walk the user through Step 2.5's design brief before discovering the name is taken.7273## Step 2.5 — Gather the design brief7475Get concrete design intent before scaffolding — a vague sense produces a "close enough" theme that76misses the request. Establish (from what the user said, or by asking):77- **Light, dark, or a day/night pair, or a single standalone theme** — these are independent78 themes; there is no structural coupling between a "day" and "night" name. A day/night pair is79 just two full themes you choose to author together and (usually, not necessarily) share a80 palette between. Also establish **full theme vs. narrow override** (a handful of tokens → use81 `references/minimal-overrides.md` instead of this flow).82- **The source of the look** — exact brand colors, a screenshot/mockup, a URL to emulate, an83 existing published theme to port (VS Code/editor/terminal — fetch its real source, never84 reconstruct from memory), or verbal direction ("dark, moody, high-contrast"). Any of these is85 real direction.8687`references/design-inputs.md` covers *how* to work each source (inspecting screenshots, pulling88real `getComputedStyle` values from a URL, porting a VS Code theme, when to lean on the89**frontend-design** skill). Read it now if the brief is anything beyond an exact hex code. Don't90re-ask for what the user already gave — confirm your reading of it.9192## Step 3 — Scaffold (deterministic)9394Run the scaffold command. It generates the new theme **directly from the canonical token tree** —95no donor theme copied, no `#FF00FF` placeholder:96- every **semantic** token (`tokens/source/**`) is seeded with its real default value — a safe,97 sensible starting point;98- every **component** token (`tokens/components/**`) is seeded as an **empty slot** for you to fill99 deliberately — a component token's value is a genuine design decision (e.g. an alert's danger100 background is a light tint, not the strong semantic danger color) and can't be safely defaulted;101- the **palette** is seeded from `classic-day`'s real neutral values, so the theme builds, renders,102 and can publish to the preview npm channel immediately.103104The result is exhaustive by construction — no classification step, no template to compare against.105106```sh107# from packages/design-tokens/108pnpm theme:scaffold <name>109```110111This also prints the component-grouped token inventory (re-print anytime with112`pnpm theme:scaffold <name> --inventory`) — use it as the backbone of the Step 3.5 design spec.113`pnpm themes:unfilled <name>` reports every component slot still empty and every palette family114still identical to the seed, so nothing gets forgotten silently.115116For a **day/night pair**: scaffold both names independently, author the day theme fully, then —117since dark mode is typically the same palette with a handful of tokens re-pointed to darker118steps (`token-model.md`) — copy the finished day theme's palette into the night theme's119`<name>.alias.color.json` as a starting point, and re-point only the tokens that should change.120This is a convenience, not a requirement: if the user wants genuinely different palettes for day121and night, author each independently. Once both are built, Step 5.5's preview can render the pair122together — `Original | <day> | <night>` in one page — instead of previewing each separately.123124## Step 3.5 — Write the design spec, and confirm it125126Turn the brief + the scaffold's grouped inventory into a short structured spec, **presented in127chat (never written to a repo file)**. This is the artifact Step 4 executes against; writing a128section per dimension forces an explicit decision — including "unchanged" — instead of relying on129remembering what matters this time. Use `references/component-tokens.md` as the mental model.130131- **Colors** — always an actively-derived section: background/surface hierarchy, borders,132 supporting hues, status colors, *not* just the one named brand color. A single hex is a133 starting point for a cohesive palette, never a ceiling. (This is the "one button recolored"134 failure mode — a color-only theme reads as the same UI tinted.)135- **Component match (the components the source shows)** — the part that most decides whether it136 *looks like* the source, and the one that's most often gotten wrong. For each key component the137 source shows (especially buttons, cards, input-type elements, badges, alerts, etc.), write a row whose138 right-hand side names *which Kong component + tokens reproduce it*, and whose left-hand side139 covers **both** halves of the treatment — never color alone:140 - *Color*: fill, text, border, and the hover/active/focus/disabled states.141 - *Geometry* (a REQUIRED field on every row, not an afterthought): `border-radius`,142 `border-width`, `padding-x`/`padding-y`, `font-size`, `font-weight`, `line-height` — each set143 to the value you measured (`getComputedStyle`), or the literal word **"unchanged"** if the144 source genuinely matches the template. A row that lists color but leaves geometry blank is an145 incomplete row: a button that is the right color and radius but the wrong padding/weight/size146 still doesn't read as the source (the exact miss that shipped once — color+radius done,147 `--kui-button-padding-*`/`-font-weight`/`-font-size-*` left at Kong's compact defaults).148 **Map by visual equivalence, not by role name**: the source's most prominent call-to-action149 becomes Kong's `primary` button and must reproduce that button's exact treatment — even if its150 color is a bright "accent-looking" one — see `component-tokens.md` "Match the source's components151 — don't recolor by role name" for the gold-pill failure this rule prevents. Set both the component152 token and its semantic fallback so it renders on whatever Kongponents version the user runs (also153 in `component-tokens.md`).154- **System propagation (every other component the source does NOT show)** — a REQUIRED spec155 section, not optional breadth. The scaffold inventory lists ~100 component groups; a source shows156 a handful. Every remaining family must carry the **same brand character** you just established,157 *derived* from what the source did let you determine — not left at Kong's defaults (a theme whose158 buttons are on-brand but whose checkboxes, switches, inputs, and cards are still Kong-gray reads159 as half-finished). Walk the inventory family by family and give each a derived treatment or an160 explicit "neutral — unchanged"; no family left un-considered. `component-tokens.md` "Propagate the161 brand across the whole component system" is the how — the derivation rules (brand hue → all "on"162 states, roundedness, focus rings, density) and which families to cover.163- **Typography, radius, shadow, spacing** — a section where the source shows a direction; mark164 "unchanged from the template" explicitly where it doesn't. Inventing a type scale or radius from165 nothing isn't breadth, it's fabrication — but leaving a distinctive one unmatched is the miss.166- If something the user described **can't** be expressed as a token value (a typewriter animation,167 ASCII art — that's component code, out of scope), say so; don't fabricate a token or drop it.168169**Confirm the spec with the user before Step 4.** A build cycle is cheap to repeat; redoing a170wrong direction across dozens of tokens is not. Lean on **frontend-design** for the aesthetic171calls (deriving a palette from one hex, matching a "vibe," state derivation, contrast).172173## Step 4 — Fill in the values174175The files already exist and are complete; now set values per the confirmed spec.1761771. **`themes/<new>/<new>.alias.color.json`** — replace the seeded classic-day values with the178 theme's real palette. Keep the exact key set; set each changed `$description` to179 `"Alias for <VALUE>."` exactly (this one *is* guard-checked). Run180 `pnpm themes:unfilled <name>` to see which families are still unchanged from the seed — nothing181 should be left unintentionally identical to classic-day's neutrals. (Skip for a night variant —182 use the day theme's finished palette per Step 3.)1832. **`themes/<new>/<new>.theme.json`** — set values across every dimension the spec addressed:184 - *Color tokens* (`{color.alias.*}` refs): point each at the step whose role/contrast fits185 *this* palette — the scaffolded value is `tokens/source`'s shared default mapping, not a186 decision specific to this theme; don't just keep it unexamined.187 - *Component match*: for each component-match row in the spec, set the component tokens **and**188 the semantic tokens they fall back to (per `component-tokens.md`) so the treatment renders on189 whatever Kongponents version the user runs. Set the **geometry** tokens alongside the color190 ones — for a button that means `--kui-button-padding-x/y-*`, `-border-radius-*`,191 `-font-weight`, `-font-size-*`, `-line-height-*`, not just the color tokens; the primary192 button's fill/text/border **and** radius/padding/weight/size must reproduce the source's193 primary CTA. The scaffold inventory grouped these by component so you can do one component at194 a time. `pnpm themes:unfilled <name>` lists every component slot still empty — work it down to195 zero (or a deliberate "neutral — unchanged" you've accounted for in the spec).196 - *System propagation*: work the "every other component" spec section too — set the197 selected/checked/active fills, focus rings, radii, and densities of the unshown component198 families to the derived brand values (per `component-tokens.md` "Propagate the brand across199 the whole component system"). A theme whose buttons are branded but whose checkboxes/switches/200 inputs/cards are still Kong-default is not finished.201 - *Literal tokens* (radius, shadow, padding, font-family, etc.): set the ones the spec called202 for. Typeface change = global find/replace of the one font-stack string across all203 `-font-family` tokens.204 - *Literal colors you introduce* (focus rings, overlays, shadows) must derive from the new205 palette (exact palette channels — see `component-tokens.md`). For a literal hex or numeric206 `rgb()`/`rgba()`, the off-source-color guard rejects an off-palette value at build time — but207 it does **not** parse `color-mix()`, `hsl()`, or CSS color keywords, so a `color-mix()` shadow208 specifically needs the same care without that safety net (see `token-model.md` "Other209 invariants" for exactly what the guard does and doesn't catch).210 - Follow the `$description` rules in `token-model.md` (nothing guards theme-token descriptions,211 so get them right up front).212213## Step 5 — Build214215```sh216pnpm --filter @kong/design-tokens test # pretest runs build:tokens; runs the guards217pnpm --filter @kong/design-tokens lint218```219220A failing guard names the exact problem (missing/extra token, stale `$description`, off-source221color) — fix that specific thing. Note: the package sandbox (`pnpm sandbox`) does **not** render222themes; use Step 5.5's preview instead.223224## Step 5.5 — Preview and verify against the source225226Structural pass (guards green) ≠ *looks right*. Close the gap with two checks:2272281. **Grep the compiled values** for what mattered most in the spec — color *and* non-color:229 ```sh230 grep -in -- "--kui-color-background-primary:" dist/themes/<new>.css231 grep -in -- "--kui-button-border-radius-medium:" dist/themes/<new>.css # etc. per spec232 ```233 Catches a token pointed at the wrong on-palette step, or a spec'd category left at the default.2342352. **Render real components under the theme and compare.** This is the check that actually proves236 fidelity, and it now works reliably:237 ```sh238 pnpm theme:preview -- <name...> [--port 8747] [--kongponents <ver>]239 # equivalent: node ../skills/theme-creation/scripts/preview.mjs <name...> [--port 8747] [--kongponents <ver>]240 ```241 (`theme:preview` is a `design-tokens/package.json` convenience alias for the skill's own242 `preview.mjs` — the script itself lives in and is owned by this skill, not the package, since243 it's a skill-verification tool with no correctness contract worth unit-testing, not a244 tokens/themes build artifact. `--` is required so pnpm forwards the theme name(s) and flags245 instead of consuming them itself.)246247 It serves a side-by-side **Original vs. themed** gallery of real `@kong/kongponents` components248 (buttons in every state, badges, cards, inputs, radios/switch, multiselect, textarea, date249 picker, code block, tabs, external link, filter group, a paginated table, modals/prompts/250 slideouts, tooltips, and alerts) loaded from a CDN — no install. Pass more than one theme name to251 compare them together — e.g. a day/night pair with252 `preview.mjs acme-day acme-night` renders `Original | acme-day | acme-night` as three columns on253 one page, which is the common case since a pair is usually eyeballed side by side rather than one254 theme at a time. (Its own generated files land in255 `packages/skills/theme-creation-workspace/preview/<port>/`, which is gitignored — you don't need256 to clean those up.) Navigate a browser tool to the printed `http://localhost:<port>/index.html`,257 screenshot it **to a path outside the repo entirely** (e.g. your scratch/tmp directory) — a258 screenshot saved anywhere inside the repo tree, even a gitignored one, is still a scope leak an259 agent should avoid creating — and view it next to the source. The modal, prompt, and slideout260 examples render behind trigger buttons, and the tooltip examples behind hover triggers — a261 single default screenshot only shows the closed triggers, so click/hover them with the browser262 tool to reveal and screenshot the themed overlay surfaces too.263264 **Preview against the Kongponents version the user actually runs.** A version only consumes the265 tokens *it* was built to read — a published version may read only the semantic fallback, so266 component tokens (button radius/padding, per-appearance colors, etc.) won't show and a correct267 theme looks under-changed. Observed concretely: the published `latest` consumes **none** of the268 `--kui-button-*` component tokens — not geometry (`-padding-*`/`-border-radius-*`/`-font-*`) and269 not per-appearance colors (`-secondary`/`-tertiary`) — it hardcodes button geometry and derives270 secondary/tertiary from the primary semantic tier. So a two-tone brand or a distinct button271 density **cannot be verified on `latest`**; you must preview against the build that consumes the272 component tokens. Set both tiers (Step 3.5) so it renders on either, and pass273 `--kongponents <version|tag>` (or `--kongponents-css`/`--kongponents-esm` for a PR/canary build)274 to match the target — ask the user which build they run if you don't know. If the source's275 component character isn't showing, first rule out version skew — don't "fix" a theme that's276 already right — but don't hide behind it either: if the target version consumes the tokens and277 it still doesn't match, the theme is wrong.278279 **Acceptance bar — this is the check the whole skill exists for.** Put the themed panel next to280 the source and compare the key components directly, component by component: does the themed281 **primary button** match the source's primary CTA in fill, text color, border, **and geometry**282 (radius, padding, font-weight, font-size)? Confirm geometry by reading it back with283 `getComputedStyle` on the themed component — a px-for-px check, not an eyeball — because a284 wrong-density button is easy to miss visually at a glance. Secondary/tertiary/danger buttons?285 Card surface and elevation? Inputs? Then check the **propagated** components the source never286 showed — checkbox/radio/switch checked fills, input focus rings, selected tabs/rows — do they287 carry the brand, or are they still Kong-gray? A "close enough" that leaves the most visible288 component visibly unlike the source (wrong color, wrong shape, wrong density), or that leaves289 whole component families un-branded, is **not done**, not a rendering caveat. Name each mismatch290 and go back to Step 3.5/4 to fix it (re-map the role, set the missing tier, set the geometry,291 propagate the brand).292293**Get the user's sign-off on the rendered result** (the screenshots + grep), not just the planned294spec, before calling it done. If no browser tool is available, restate the confirmed values —295color *and* component/typography, component by component against the source — and have the user296preview the CSS themselves; don't declare victory on guards alone.297298**A good preview does not prove completeness.** An unfilled component token compiles to nothing at299all (never `--x: ;` or `--x: initial;`), so it falls through to its semantic default exactly as300gracefully as a *deliberately* unbranded family would — the preview can't tell those two cases301apart. Run `pnpm themes:unfilled <name>` before calling it done; it's the only check that actually302distinguishes "still empty" from "intentionally at the default."303304## Step 6A — In-repo: done305306`git status` should show exactly the new `themes/<new>/` directory (its two files) — commit those307(no `themes.spec.mjs` change; it classifies the new theme as exhaustive automatically).308`dist/themes/<new>.*` regenerates on build; nothing to extract. **No `kong-konnect/portal`309override is needed for a theme created by this skill** — that concern applies *only* to changes to310`classic-day`/`classic-night`.311312## Step 6B — Standalone: extract, then tear down3133141. **Iterate** on Steps 4–5.5 with the user until the preview looks right.3152. **Extract** the deliverable: `dist/themes/<new>.css` (a self-contained316 `@layer kui.theme { [data-kui-theme="<new>"] {…} }` block; also `.mjs`/`.cjs`/`.d.ts` if they317 want the JS object). Ask where to copy it. Verify it's fully resolved — no `{color.alias…}`,318 no `var(--kui-color-alias…)`, no `: undefined;`; if any appear, rebuild before handing off.3193. **Tear down** — leave the repo exactly as it started:320 ```sh321 pnpm theme:scaffold <name> --teardown322 pnpm --filter @kong/design-tokens test # confirm green323 ```324 Then `git status` — no lingering diff in `packages/design-tokens/`.