A powerful, framework-agnostic theme management library with AI-powered theme generation.
- Framework Agnostic - Works with vanilla JS, React, Vue, or any framework
- AI-Powered Theme Generation - Generate beautiful themes from text descriptions using OpenAI, Claude, or custom AI providers
- Custom Structured Data - Attach arbitrary JSON data to any theme; AI can generate it alongside tokens
- CSS Variables - Non-invasive styling using CSS Custom Properties
- Built-in Themes - 7 beautiful pre-designed themes out of the box
- Type-Safe - Full TypeScript support with comprehensive type definitions
- Persistent - Built-in localStorage and IndexedDB support for theme persistence
- WCAG Compliance - Utilities for checking color contrast and accessibility
| Package | Description |
|---|---|
@themed.js/core |
Core library with theme management, AI integration, and storage |
@themed.js/react |
React bindings with hooks and context provider |
@themed.js/vue |
Vue 3 bindings with composables and plugin |
# Core only (vanilla JS)
npm install @themed.js/core
# With React
npm install @themed.js/core @themed.js/react
# With Vue
npm install @themed.js/core @themed.js/vueimport { createThemed } from '@themed.js/core';
const themed = createThemed({
defaultTheme: 'light',
ai: {
provider: 'openai',
apiKey: 'sk-xxx',
},
});
await themed.init();
// Switch themes
themed.apply('dark');
// Generate AI theme
const theme = await themed.generate('A warm autumn sunset theme');
// Generate with custom structured data
const themed2 = await themed.generate('A corporate blue theme', {
customSchema: 'Brand guidelines with name, tone of voice, and target audience',
});
// theme.custom → { "brandName": "...", "tone": "...", "audience": "..." }import { ThemeProvider, useTheme, useAITheme } from '@themed.js/react';
function App() {
return (
<ThemeProvider
defaultTheme="light"
ai={{ provider: 'openai', apiKey: 'sk-xxx' }}
>
<MyApp />
</ThemeProvider>
);
}
function ThemeSwitcher() {
const { themes, apply } = useTheme();
const { generate, isGenerating } = useAITheme();
return (
<div>
{themes.map(t => (
<button key={t.id} onClick={() => apply(t.id)}>
{t.name}
</button>
))}
<button onClick={() => generate('Ocean sunset')}>
AI Generate
</button>
</div>
);
}<script setup>
import { useTheme, useAITheme } from '@themed.js/vue';
const { themes, apply } = useTheme();
const { generate, isGenerating } = useAITheme();
</script>
<template>
<button
v-for="t in themes"
:key="t.id"
@click="apply(t.id)"
>
{{ t.name }}
</button>
</template>// main.ts
import { createApp } from 'vue';
import { themedPlugin } from '@themed.js/vue';
const app = createApp(App);
app.use(themedPlugin, {
defaultTheme: 'light',
ai: { provider: 'openai', apiKey: 'sk-xxx' },
});Themed.js injects CSS variables that you can use in your stylesheets:
.button {
background-color: var(--themed-color-primary);
color: var(--themed-color-text-inverse);
font-family: var(--themed-font-family-sans);
font-size: var(--themed-font-size-base);
}
.card {
background-color: var(--themed-color-surface);
border: 1px solid var(--themed-color-border);
}Every theme can carry an optional custom field — an arbitrary JSON object — that travels with the theme through storage, export/import, and AI generation.
// Vanilla
themed.updateThemeCustom('my-theme', {
brandName: 'Acme Corp',
tone: 'professional',
audience: 'enterprise',
});
themed.apply('my-theme'); // triggers re-render in reactive frameworks// React
const { theme, updateThemeCustom, apply } = useTheme();
updateThemeCustom(theme.id, { brandName: 'Acme', tone: 'friendly' });
apply(theme.id);<!-- Vue -->
<script setup>
const { theme, updateThemeCustom, apply } = useTheme();
function attach(custom) {
updateThemeCustom(theme.value.id, custom);
apply(theme.value.id);
}
</script>import { createTheme } from '@themed.js/core';
const theme = createTheme({
id: 'brand',
name: 'Brand Theme',
tokens: { /* ... */ },
custom: {
brandName: 'Acme Corp',
primaryUsage: 'Marketing site',
},
});Pass a customSchema when generating a theme. It can be a natural-language description or a JSON skeleton with placeholder values:
// Natural language
const theme = await themed.generate('A warm startup theme', {
customSchema: 'Brand guidelines with company name, tagline, tone of voice, and target audience',
});
// JSON skeleton — AI fills in the values to match the theme
const theme2 = await themed.generate('A dark fintech theme', {
customSchema: '{ "brandName": "...", "tone": "...", "audience": "..." }',
});
console.log(theme.custom);
// { "brandName": "...", "tone": "...", "audience": "...", ... }The custom field is included in export/import and persisted to storage automatically.
- Light - Clean, modern light theme
- Dark - Comfortable dark theme
- Ocean - Calm blue tones
- Forest - Natural green tones
- Sunset - Warm gradient colors
- Midnight - Deep dark with purple accents
- Rose - Soft pink tones
Themed.js supports multiple AI providers:
// OpenAI (default: gpt-4o-mini)
ai: {
provider: 'openai',
apiKey: 'sk-xxx',
model: 'gpt-4o-mini', // optional: gpt-4o, gpt-4-turbo, etc.
}
// Claude (default: claude-sonnet-4-6)
ai: {
provider: 'claude',
apiKey: 'sk-xxx',
model: 'claude-sonnet-4-6', // optional: claude-opus-4-6, claude-haiku-4-5
}
// Google Gemini (default: gemini-2.5-flash)
ai: {
provider: 'gemini',
apiKey: 'xxx', // from Google AI Studio
model: 'gemini-2.5-flash', // optional: gemini-2.5-pro, gemini-2.0-flash
}
// Groq - Llama models (default: llama-3.3-70b-versatile)
ai: {
provider: 'groq',
apiKey: 'gsk_xxx', // from console.groq.com
model: 'llama-3.3-70b-versatile',
}
// Moonshot/Kimi (default: kimi-k2-turbo-preview)
ai: {
provider: 'moonshot',
apiKey: 'xxx', // from platform.moonshot.ai
model: 'kimi-k2-turbo-preview',
baseURL: 'https://api.moonshot.cn/v1', // optional: use .cn for China
}
// DeepSeek (default: deepseek-chat)
ai: {
provider: 'deepseek',
apiKey: 'xxx', // from platform.deepseek.com
model: 'deepseek-chat', // optional: deepseek-reasoner
}
// Custom endpoint
ai: {
provider: 'custom',
endpoint: 'https://your-api.com/generate',
apiKey: 'xxx', // optional
}
// Chrome extension proxy — no API key in the page (see below)
ai: {
provider: 'extension',
}The extension provider delegates all LLM calls to the Themed LLM Secure Proxy Chrome extension via window.ThemedLLM. Your API key never appears in page code — it is stored and used only inside the extension.
Setup:
- Install (or load unpacked) the Themed LLM Secure Proxy Chrome extension.
- Open the extension's options page and configure your AI provider, model, and API key.
- Use
provider: 'extension'in your app — noapiKey,model, orbaseURLneeded:
import { createThemed } from '@themed.js/core';
const themed = createThemed({
defaultTheme: 'zinc',
ai: {
provider: 'extension',
},
});
await themed.init();
const theme = await themed.generate('A warm sunset theme');
// All LLM traffic goes through the extension — no key in this page.If the extension is not installed or not active when generate() is called, a clear error is thrown:
Themed LLM Proxy extension is not detected.
Install it or load it unpacked, then refresh the page.
const themed = createThemed(options);
// Lifecycle
await themed.init();
themed.destroy();
// Theme management
themed.register(theme); // Register a theme
themed.registerMany([...themes]); // Register multiple themes
themed.unregister(themeId); // Remove a theme
themed.apply(themeId); // Apply a theme by ID
themed.getActive(); // Get the currently active Theme
themed.get(themeId); // Get a theme by ID
themed.getAll(); // Get all registered themes
themed.has(themeId); // Check if a theme is registered
// Custom structured data
themed.updateThemeCustom(themeId, custom); // Set custom data on a theme
// Note: call apply(themeId) afterwards to trigger reactive updates
// AI generation
await themed.generate(prompt, options?); // Generate a theme from a text prompt
// options.customSchema — natural language or JSON skeleton for custom data to co-generate
// options.autoApply — auto-apply after generation (default: true)
// options.autoSave — auto-save to storage after generation (default: true)
// options.baseTheme — adjust an existing theme instead of generating from scratch
// Runtime configuration
themed.configureAI(aiOptions); // Configure AI at runtime (e.g. after user enters API key)
themed.configureStorage(opts); // Reconfigure storage
themed.configureCSS(opts); // Reconfigure CSS variable injection
// Events (see docs/EVENTS.md for the full event contract)
themed.on('theme:changed', ({ theme }) => {});
themed.on('theme:generating', ({ prompt }) => {});
themed.on('theme:generated', ({ theme, prompt, duration }) => {});
themed.on('theme:error', ({ error, context }) => {});
themed.off(event, handler);interface Theme {
id: string;
name: string;
description?: string;
tokens: ThemeTokens;
/** Arbitrary JSON data attached to this theme (brand guidelines, metadata, etc.) */
custom?: Record<string, unknown>;
meta: {
version: string;
createdAt: number;
source: 'builtin' | 'user' | 'ai';
aiPrompt?: string;
};
}interface ThemeTokens {
colors: {
primary: string;
secondary: string;
accent: string;
background: string;
surface: string;
error: string;
warning: string;
success: string;
info: string;
textPrimary: string;
textSecondary: string;
textDisabled: string;
textInverse: string;
border: string;
borderLight: string;
borderDark: string;
};
typography: {
fontFamily: { sans: string; serif: string; mono: string };
fontSize: { xs, sm, base, lg, xl, '2xl', '3xl': string };
fontWeight: { light, normal, medium, semibold, bold: number };
lineHeight: { tight, normal, relaxed: number };
};
spacing?: Record<string, string>; // e.g. { sm: '0.5rem', md: '1rem', ... }
radius?: Record<string, string>; // e.g. { sm: '0.25rem', full: '9999px', ... }
shadow?: Record<string, string>; // e.g. { sm: '0 1px 2px ...', md: '...', ... }
transition?: Record<string, string>;
}interface GenerateOptions {
/** Auto-apply the generated theme (default: true) */
autoApply?: boolean;
/** Auto-save to storage (default: true) */
autoSave?: boolean;
/** Base theme to adjust instead of generating from scratch */
baseTheme?: Theme;
/**
* Describe custom structured data to generate alongside the theme tokens.
* Accepts natural language or a JSON skeleton with placeholder values.
*
* @example "Brand guidelines with name, tone, and target audience"
* @example '{ "brandName": "...", "tone": "...", "audience": "..." }'
*/
customSchema?: string;
}The repo includes a workflow that builds the React, Vue, and Vanilla examples and deploys them to GitHub Pages.
-
Enable GitHub Pages
In the repo: Settings → Pages → Build and deployment:- Source: GitHub Actions.
-
Push to
main
The workflow runs on every push tomain(or trigger it manually via Actions → Deploy to GitHub Pages → Run workflow). -
Open the site
After deployment, the site is at:
https://<your-username>.github.io/themed.js/
API keys are not embedded; users enter their own key in each demo's UI (safe for public hosting).
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test:run
# Start example apps
cd examples/vanilla && pnpm dev # Port 3000
cd examples/react && pnpm dev # Port 3001
cd examples/vue && pnpm dev # Port 3002- Bump version in each package:
packages/*/package.json(e.g.0.1.0→0.1.1). - Build and publish all packages:
pnpm build pnpm publish -r --no-git-checks
- Or publish a single package:
pnpm --filter @themed.js/core publish --no-git-checks.
Note: Scoped packages (@themed.js/*) are public via publishConfig.access. Ensure you are logged in to npm (npm login) and have access to the themed.js scope (create the org at npmjs.com if needed).
MIT