@cdx-ui/theme-editor
v0.0.1-beta.60
Published
A guided, web-only editor for authoring Forge Design System `ThemeOverride` JSON. Financial institutions pick a base preset, display font, and brand/accent colors through MUI form controls while a live panel previews real Forge UI components rendered with
Readme
@cdx-ui/theme-editor
A guided, web-only editor for authoring Forge Design System ThemeOverride JSON. Financial institutions pick a base preset, display font, and brand/accent colors through MUI form controls while a live panel previews real Forge UI components rendered with the resulting theme. The editor emits a ThemeOverride payload that consuming apps persist and apply at runtime via applyThemeOverride from @cdx-ui/styles.
Web-only. This package depends on MUI (React DOM) and React Native Web. It is not available on native platforms.
Installation
pnpm add @cdx-ui/theme-editor @cdx-ui/stylesreact, react-dom, react-native-web, and uniwind are peer dependencies — apps already consuming @cdx-ui/components will have these in place. @cdx-ui/components, @cdx-ui/icons, and @cdx-ui/styles are resolved automatically.
Usage
1. Add the editor to your Tailwind sources
The preview renders Forge UI components with Uniwind utility classes. Like the
other @cdx-ui/* packages, this package ships no precompiled CSS or tokens —
its ./styles.css is a Tailwind v4 @source directive that adds the editor's
source to your content scan so the preview's utilities are generated. Import it
in your Tailwind entry stylesheet, alongside the rest of the design system:
@import 'tailwindcss';
@import 'uniwind';
@import '@cdx-ui/styles/theme.css';
@import '@cdx-ui/styles/utilities.css';
@import '@cdx-ui/components/styles.css';
@import '@cdx-ui/theme-editor/styles.css';2. Mount the editor
onChange fires (debounced) on every valid edit with the current ThemeOverride. Persist it, and apply it at runtime with applyThemeOverride:
import { ThemeEditor } from '@cdx-ui/theme-editor';
import { applyThemeOverride } from '@cdx-ui/styles';
import type { ThemeOverride } from '@cdx-ui/styles';
function ThemeConfigPage() {
const handleChange = (override: ThemeOverride) => {
applyThemeOverride(override); // live-apply to the document
void persistOverride(override); // your save (e.g. POST /api/themes)
};
return <ThemeEditor onChange={handleChange} showGetCode showThemeModeToggle />;
}The editor also calls applyThemeOverride internally to drive its own preview, so the surrounding page reflects the in-progress theme.
3. Edit an existing override
Pass a previously saved override as initialValue to enter edit mode; the controls seed from its inputs:
<ThemeEditor initialValue={savedOverride} onChange={handleChange} />Props
| Prop | Type | Default | Description |
| --------------------- | ----------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| onChange | (override: ThemeOverride) => void | — | Required. Called (debounced, ~200ms) with the current ThemeOverride on each valid edit. Not called while a color input is invalid. |
| initialValue | ThemeOverride | — | Pre-populates the editor for edit mode. When omitted, controls seed from the Poise defaults. |
| showGetCode | boolean | false | Shows a "Get code" menu item that opens a popover with the override output and a copy-to-clipboard button. |
| showThemeModeToggle | boolean | false | Shows a light/dark toggle that switches the preview surface. |
The ThemeOverride shape (and applyThemeOverride) are documented in @cdx-ui/styles and Override Structure.
Behavior notes
- Defaults. With no
initialValue, the controls seed from Poise (brand#346099,accent#ffcf23, display fontCrimson Pro) so the preview is a sensible default rather than empty. Brand/accent colors are seeds — the full palette is regenerated from them at runtime, so the untouched preview approximates, but is not pixel-identical to, the built-in Poise theme. - Live preview.
applyThemeOverridewrites CSS variables to the document root; the editor owns the document theme while mounted. - Preview isolation. The preview is wrapped in
ScopedThemeso the light/dark toggle only affects the preview surface, not the editor chrome.
Package structure
theme-editor/
├── src/
│ ├── index.ts # Public exports (ThemeEditor, ThemeEditorProps)
│ ├── styles.css # `@source` directive — published as the ./styles.css export
│ ├── ThemeEditor.tsx # Top-level component: state, debounced apply/emit, popover, toggle
│ ├── PreviewPanel.tsx # Live preview surface (ScopedTheme + banking artboard)
│ ├── reducer.ts # Editor state + Poise seed defaults
│ ├── buildOverrideOutput.ts # Editor state → ThemeOverride (round-trip safe)
│ ├── validation.ts # Hex validation
│ ├── fonts.ts # Google Fonts loading for preset display fonts
│ ├── controls/ # PresetControl, FontControl, ColorControl
│ ├── components/ # ColorPickerInput, PresetPreview
│ └── previews/ # Forge UI sample cards rendered in the preview
├── global.css # Tailwind/Uniwind entry for the build (not published)
├── vite.config.ts
└── package.jsonBuilding
pnpm --filter @cdx-ui/theme-editor buildVite bundles src/index.ts to dist/index.js (externalizing React, React Native Web, Uniwind, and the @cdx-ui/* packages); tsc emits declarations. No CSS is emitted — src/styles.css (the @source directive) is published as-is and composes with the consumer's Tailwind build.
Further reading
- Override Structure —
ThemeOverridepayload format and responsibility contract - Runtime Theming Visual Guide — build-time vs runtime data flow
- @cdx-ui/styles — tokens, presets, and
applyThemeOverride
