Package Exports
- @themal/editor
- @themal/editor/style.css
Readme
@themal/editor
Interactive design system editor for React apps. Pick colors, generate harmony palettes, enforce WCAG AA contrast, customize typography and interaction states, and export CSS custom properties — all in real time. Fully responsive — works on desktop, tablet, and mobile.
Free tier available with core features. Pro features ($9/mo or $50/yr) require a subscription.
Install
npm install @themal/editorPeer Dependencies
| Package | Version | Required |
|---|---|---|
react |
^18.0.0 || ^19.0.0 |
Yes |
react-dom |
^18.0.0 || ^19.0.0 |
Yes |
axe-core |
>=4.0.0 |
Optional — enables accessibility auditing |
lucide-react |
>=0.294.0 |
Optional — enables icon previews |
Install optional peers for full functionality:
npm install axe-core lucide-reactQuick Start
import { DesignSystemEditor } from '@themal/editor';
import '@themal/editor/style.css';
function App() {
return <DesignSystemEditor />;
}The editor writes CSS custom properties (HSL values) to :root and injects global typography styles, so colors and fonts apply across your entire site.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
licenseKey |
string |
— | License key to unlock premium features. |
upgradeUrl |
string |
"/pricing" |
URL shown in premium gate upgrade prompts. |
signInUrl |
string |
— | URL shown in premium gate sign-in prompts. |
prEndpointUrl |
string |
— | URL for PR creation endpoint. PR button hidden if omitted. |
accessibilityAudit |
boolean |
true |
Enable axe-core color contrast auditing. |
onChange |
(colors: Record<string, string>) => void |
— | Callback on every color change with the full color map. |
onExport |
(css: string) => void |
— | Override built-in CSS modal. Receives the generated CSS string. |
className |
string |
— | Additional CSS class for the wrapper element. |
showHeader |
boolean |
true |
Show the editor header with logo and navigation. |
showNavLinks |
boolean |
true |
Show page navigation links in the header and mobile menu. |
headerRight |
React.ReactNode |
— | Custom content rendered on the right side of the header (e.g. auth buttons). |
aboutUrl |
string |
— | URL for the About page link in the header navigation. |
customIcons |
CustomIcon[] |
— | Custom icons to display in the Icons preview section. Each entry needs name and icon (a React component). |
iconMode |
"append" | "replace" |
"append" |
"append" adds custom icons after the built-in lucide icons. "replace" hides built-ins and shows only custom icons. |
showLogo |
boolean |
true |
Show the Themal logo in the header. Set false for white-label or plugin usage. |
Premium Features
The following features require a valid license key:
| Feature | Description |
|---|---|
| Harmony schemes | Generate palettes using complementary, analogous, triadic, or split-complementary color relationships. |
| Color locks | Lock up to 4 colors to preserve them during palette regeneration. |
| PR integration | Create design system pull requests directly from the editor. |
| Undo/redo | Up to 10 levels of undo for theme refreshes and image palette applications. |
| Image palette extraction | Extract color palettes from uploaded images with preview confirmation. |
| Palette export | Download palette as SVG/PNG, or copy as HEX/RGB/RGBA text. |
| Interaction states | Style hover, focus, and active states for buttons and components. |
| Typography interactions | Customize hover, active, and underline behavior for links and headings. |
| Custom fonts | Add any Google Font by name — validated, loaded, and persisted across sessions. |
License Key Format
Keys follow the format THEMAL-XXXX-XXXX-XXXX with a checksum-validated third segment. Use generateLicenseKey() to create valid keys.
PremiumGate Component
Wrap any feature in PremiumGate to gate it behind a license key. Clicking a gated feature opens a modal with upgrade and sign-in options:
import { PremiumGate } from '@themal/editor';
<PremiumGate feature="harmony-schemes" upgradeUrl="/pricing" signInUrl="/sign-in">
<HarmonyControls />
</PremiumGate>| Prop | Type | Default | Description |
|---|---|---|---|
feature |
string |
— | Name of the premium feature being gated. |
variant |
"section" | "inline" |
"section" |
"section" dims content with a lock overlay; "inline" shows a lock icon inline. Both open a modal on click. |
upgradeUrl |
string |
"/pricing" |
URL for the upgrade prompt. |
signInUrl |
string |
— | URL for the sign-in prompt. |
LicenseProvider
If using PremiumGate or useLicense outside of DesignSystemEditor, wrap your tree in LicenseProvider:
import { LicenseProvider } from '@themal/editor';
<LicenseProvider licenseKey="THEMAL-XXXX-XXXX-XXXX">
<App />
</LicenseProvider>Usage Examples
Basic — color picker only
<DesignSystemEditor accessibilityAudit={false} />With PR creation
<DesignSystemEditor prEndpointUrl="/api/create-design-pr" />With premium features
<DesignSystemEditor
licenseKey="THEMAL-XXXX-XXXX-XXXX"
upgradeUrl="/pricing"
signInUrl="/sign-in"
/>With custom header content
<DesignSystemEditor
headerRight={<button onClick={handleSignIn}>Sign In</button>}
aboutUrl="/about"
/>Listen for changes
<DesignSystemEditor
onChange={(colors) => {
console.log('Brand color:', colors['--brand']);
}}
/>Custom export handler
<DesignSystemEditor
onExport={(css) => {
navigator.clipboard.writeText(css);
}}
/>With custom icons
import { Rocket, Star, Flame } from 'lucide-react';
// Or use any React component that accepts className
import { MyBrandIcon } from './icons';
<DesignSystemEditor
customIcons={[
{ name: "Rocket", icon: Rocket },
{ name: "Star", icon: Star },
{ name: "Flame", icon: Flame },
{ name: "Brand", icon: MyBrandIcon },
]}
/>Replace built-in icons entirely
<DesignSystemEditor
customIcons={[
{ name: "Brand", icon: MyBrandIcon },
{ name: "Product", icon: ProductIcon },
]}
iconMode="replace"
/>The Icons section includes a "Hide All" / "Show All" toggle so users can collapse the icon grid.
Embedded / headless
<DesignSystemEditor
showHeader={false}
showNavLinks={false}
/>Exported Utilities
import {
// Color utilities
hslStringToHex, // "210 50% 40%" -> "#336699"
hexToHslString, // "#336699" -> "210.0 50.0% 40.0%"
contrastRatio, // WCAG contrast ratio between two HSL strings
fgForBg, // Best foreground (black/white) for a background HSL
EDITABLE_VARS, // Array of { key, label } token definitions
HARMONY_SCHEMES, // ['Complementary', 'Analogous', 'Triadic', 'Split-Complementary']
applyStoredThemeColors, // Restore persisted theme from localStorage
// localStorage key constants
CARD_STYLE_KEY, // Key for card style storage
TYPOGRAPHY_KEY, // Key for typography storage
ALERT_STYLE_KEY, // Key for dialog alert style storage
TOAST_STYLE_KEY, // Key for toast style storage
INTERACTION_STYLE_KEY, // Key for interaction style storage
// Card, typography & interaction style utilities
applyStoredCardStyle, // Restore card style from localStorage
applyStoredTypography, // Restore typography from localStorage (applies site-wide)
applyStoredAlertStyle, // Restore dialog alert style from localStorage
applyStoredToastStyle, // Restore toast style from localStorage
applyStoredInteractionStyle, // Restore button interaction style from localStorage
// Shareable URL utilities
serializeThemeState, // Encode full theme state as base64 string
deserializeThemeState, // Decode base64 string back to theme state
// Export utilities
generateDesignTokens, // Generate W3C Design Token JSON from theme state
exportPaletteAsText, // Export palette as HEX, RGB, or RGBA text
exportPaletteAsSvg, // Export palette as SVG string
exportPaletteAsPng, // Export palette as PNG Blob
// Custom font utilities
getCustomFonts, // Load custom fonts from localStorage
addCustomFont, // Validate & add a Google Font by name
removeCustomFont, // Remove a custom font by label
initCustomFonts, // Re-register all custom fonts on startup
// License utilities
validateLicenseKey, // Validate a THEMAL-XXXX-XXXX-XXXX key
generateLicenseKey, // Generate a valid license key
// Premium components & hooks
LicenseProvider, // Context provider for license state
useLicense, // Hook: { isValid, isPremium }
PremiumGate, // Gate component for premium features
} from '@themal/editor';Exported Types
import type {
DesignSystemEditorProps,
TokenDefinition,
CustomIcon,
HarmonyScheme,
CardStyleState,
TypographyState,
AlertStyleState,
ToastStyleState,
InteractionStyleState,
TypoInteractionStyleState,
CustomFontEntry,
PremiumFeature,
LicenseValidation,
LicenseProviderProps,
PremiumGateProps,
} from '@themal/editor';How It Works
- Color picking — Click any swatch to scroll the Colors section into view, then open the native color picker. Changing a key color (brand, secondary, accent, background) automatically derives related tokens.
- Harmony schemes (Pro) — Generate palettes using complementary, analogous, triadic, or split-complementary color relationships.
- Contrast enforcement — Every foreground/background pair is checked against WCAG AA (4.5:1). Failing pairs are auto-corrected by adjusting lightness. The accessibility audit shows a centered modal with results. On failure, choose "Ignore" to dismiss or "Suggest Alternative" to auto-fix contrast issues. A WCAG On/Off toggle lets you disable auto-correction for marketing or other contexts that don't require WCAG compliance. Locks are still honored when enforcement is off.
- Typography — Choose heading and body fonts (including custom Google Fonts), adjust sizes, weights, line height, and letter spacing with live preview. Five built-in presets (System, Modern, Classic, Compact, Editorial). Typography applies site-wide, not just within the editor component, so toggling fonts updates the entire page.
- Button interactions (Pro) — Fine-tune hover opacity, hover/active scale, transition duration, and focus ring width with presets (Subtle, Elevated, Bold).
- Typography interactions (Pro) — Customize link hover/active behavior (opacity, scale, underline) and heading hover effects with live preview.
- Persistence — All settings (colors, typography, card styles, dialog styles, toast styles, interactions) are saved to
localStorageand restored on reload. - Per-section export — Every section header includes a CSS | Tokens split button to export CSS custom properties with Tailwind config, or W3C Design Token JSON, for that section.
- Shareable URLs — Encode your full theme state in the URL hash and share it with anyone via a single link.
- Palette export (Pro) — Download your palette as SVG or PNG, or copy as a HEX/RGB/RGBA text list.
- Custom fonts (Pro) — Add any Google Font by name. The editor validates the font exists, loads all weights, adds it to heading/body dropdowns, and persists it across sessions.
- Mobile friendly — Fully responsive UI with a 2D color spectrum picker (saturation/lightness canvas + hue slider) for intuitive touch-based color selection, mobile-optimized dropdowns, compact swatch labels, and stacked layouts for smaller screens.
Package Architecture
The editor is built with Vite in library mode and published as an ES module:
@themal/editor
├── dist/index.js # ESM bundle (all components + utilities)
├── dist/index.d.ts # TypeScript declarations
└── dist/style.css # Pre-compiled, scoped Tailwind stylesExports Map
{
".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
"./style.css": "./dist/style.css"
}Import the main entry point for components and utilities, and style.css separately for styles. This keeps CSS opt-in and avoids side effects during tree-shaking.
Tailwind Scoping
The editor ships pre-compiled CSS via @themal/editor/style.css. Styles are scoped using Tailwind's important: '.ds-editor' so they don't conflict with your app's styles. The root element is automatically wrapped in <div className="ds-editor">.
Web Component
The editor is also available as a <themal-editor> custom element for non-React sites (WordPress, Shopify, plain HTML).
Basic usage
<script src="https://unpkg.com/@themal/editor/dist/themal-editor.js"></script>
<themal-editor></themal-editor>Attributes
All props that accept strings or booleans can be set as HTML attributes using kebab-case:
<themal-editor
license-key="THEMAL-XXXX-XXXX-XXXX"
show-header="false"
show-nav-links="false"
icon-mode="replace"
show-logo="true"
accessibility-audit="true"
upgrade-url="/pricing"
sign-in-url="/sign-in"
about-url="/about"
pr-endpoint-url="/api/create-design-pr"
></themal-editor>Note: The Themal logo is hidden by default in the web component. Set show-logo="true" to display it.
Custom icons via JavaScript
Since HTML attributes can only pass strings, custom icons are set programmatically with the setIcons() method. Pass an array of { name, svg } objects where svg is a raw SVG string:
<script src="https://unpkg.com/@themal/editor/dist/themal-editor.js"></script>
<themal-editor icon-mode="replace"></themal-editor>
<script>
const editor = document.querySelector('themal-editor');
editor.setIcons([
{
name: "Heart",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>'
},
{
name: "Star",
svg: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>'
},
]);
</script>Set icon-mode="replace" to hide the built-in icons and show only your custom set, or omit it (defaults to "append") to add yours alongside the built-ins.
Development
# From the repo root
npm install
# Build the package
cd packages/editor
npm run build
# Watch mode
npm run devTesting
The project includes automated accessibility testing:
# Run all tests (includes axe-core a11y checks)
npm run test:run
# Lint for accessibility issues
npm run lintThe test suite uses vitest-axe to run axe-core against rendered components, catching WCAG violations automatically. ESLint with eslint-plugin-jsx-a11y provides static analysis for common accessibility anti-patterns.
Publishing to npm
The package is published as @themal/editor on npm. To publish a new version:
# 1. Build
cd packages/editor
npm run build
# 2. Verify the tarball contents (should only include dist/)
npm pack --dry-run
# 3. Bump the version
npm version patch # or minor / major
# 4. Publish (scoped packages require --access public on first publish)
npm publish --access publicYou must be logged in to npm (npm login) with publish access to the @themal scope.
License
MIT