Skip to content

Theme builder: interactive token editor with downloadable theme file #23

Description

@minimaldesign

Summary

Add an interactive theme builder page to the docs site: users edit design tokens, watch framework components restyle live, then download a ready-to-use settings.theme.custom.css file.

This is the follow-up to the component playground (_Playground.jsx, piloted on the Button page). The design below was worked out alongside it.

Design

Page

New docs page at /docs/theme-builder, authored as src/content/docs/theme-builder.mdx. The docs collection schema already fits ({ title, lastUpdate, version }), so no routing work. The page contains intro prose, the <ThemeBuilder client:load /> island (load, not visible: default-value probing should run immediately), and a static preview strip of raw-HTML framework components (button variants, badge, card, notice, form field, headings, links) that live token overrides restyle.

Also: add a nav entry in src/components/NavDocs.astro next to the Themes link, and cross-link from src/content/docs/themes.mdx.

Token metadata: hand-maintained manifest, defaults read from the live page

New src/scripts/themeTokens.js with curated groups. Values are omitted on purpose to avoid drift: at mount, read each token's current value via getComputedStyle(document.documentElement).getPropertyValue(name) and convert to hex for <input type="color">.

Chosen over parsing settings.theme.default.css?raw at build time because curation, grouping, labels, and control types would need a manifest anyway, and the raw values are light-dark(var()) chains that must be runtime-resolved regardless.

v1 scope (roughly 50 tokens, all primitives, matching the intended theme-file shape shown by the commented-out settings.theme.wireframe.css):

  • Primary scale --primary-50..950, neutrals --base-0..950, feedback --yes|no|maybe-100..500 (type color)
  • Radii --radius-sm..xxl (type length)
  • Font stacks --display, --text, --mono (type fontStack)

Because the semantic layer in settings.theme.default.css derives from these via light-dark(var(--base-X), var(--base-Y)), editing primitives re-themes both light and dark modes automatically. Editing semantic light-dark() pairs directly (two inputs per token, resolved via hidden probe elements with forced color-scheme) is v2.

Island: src/components/_ThemeBuilder.jsx

  • Controls: color input plus hex text field per color token; text inputs for lengths and font stacks. Per-group and global reset. (OKLCH sliders from _ColorPickerOklch.jsx are a possible v2 upgrade.)
  • Live preview: assemble :root { ... } from edited tokens only and inject via <style dangerouslySetInnerHTML> (same pattern as _GridDemo.jsx). Global on purpose: the page is dedicated, and an unlayered body <style> beats the layered framework declarations. The existing ThemeToggle keeps working since light/dark resolves downstream in light-dark().
  • Output: generated CSS shown live-highlighted via useHighlightedCode(css, "css"), a <CopyButton />, and a "Download settings.theme.custom.css" button (Blob + URL.createObjectURL + temporary <a download> + revokeObjectURL). The file matches the wireframe theme shape: header usage comment (import after mcss.css, link to /docs/themes), grouped section comments, 2-space indent, and only tokens that differ from defaults.

Supporting files

  • src/styles/site/component.themeBuilder.css (new), registered unlayered in src/styles/_global.css

Prior art in the repo

  • _GridDemo.jsx: settings state driving live <style> injection and a highlighted code panel
  • _ColorPickerOklch.jsx: slider-driven color editing with gamut math
  • _useHighlightedCode.js + scripts/shikiHighlighter.js: client-side Shiki
  • settings.theme.wireframe.css (commented out): the shape the downloaded theme file should match

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions