Design tokens shouldn't be scattered across your codebase. ThemeKit gives you one place to define colors, typography, spacing—everything—and use them anywhere. React, Vue, Angular, vanilla JS. Doesn't matter.
If you've ever had to update a primary color in 47 different files, you know the pain. ThemeKit fixes that. Define your tokens once, use them everywhere, switch themes on the fly.
What you get:
- Works with React, Vue, Angular, or plain JavaScript
- Full TypeScript support (types are auto-generated)
- Light/dark mode with persistence built in
- CLI to spit out CSS variables, SCSS, TypeScript—whatever you need
- Validation so your tokens stay consistent
- Runtime alias resolution with circular reference protection (opt-in)
- Prefetch + cache themes so switches stay instant
npm install @quefep/theme-kitCreate a tokens.json (you can also pass a JS object or a map of theme names ➝ tokens straight into the config):
{
"colors": {
"primary": "#4A90E2",
"secondary": "#50E3C2",
"background": "#FFFFFF",
"text": "#333333"
},
"typography": {
"fontFamily": "Inter, sans-serif",
"fontSize": {
"sm": "12px",
"md": "16px",
"lg": "20px"
}
},
"spacing": {
"sm": "4px",
"md": "8px",
"lg": "16px"
}
}import { ThemeKit } from '@quefep/theme-kit';
import tokens from './tokens.json';
const theme = new ThemeKit({
defaultTheme: 'light',
tokens,
});React:
import { ThemeProvider, useTheme } from '@quefep/theme-kit/react';
function App() {
return (
<ThemeProvider theme={theme}>
<MyButton />
</ThemeProvider>
);
}
function MyButton() {
const { getToken } = useTheme();
return (
<button style={{ backgroundColor: getToken('colors.primary') }}>
Click me
</button>
);
}Vue:
import { createApp } from 'vue';
import { ThemeKitPlugin } from '@quefep/theme-kit/vue';
createApp(App).use(ThemeKitPlugin, { theme });<template>
<button :style="{ backgroundColor: primaryColor }">Click me</button>
</template>
<script setup>
import { useColor } from '@quefep/theme-kit/vue';
const primaryColor = useColor('primary');
</script>Angular:
import { ThemeKitModule } from '@quefep/theme-kit/angular';
@NgModule({
imports: [ThemeKitModule.forRoot({ theme })],
})
export class AppModule {}Vanilla JS:
import { ThemeKit, createVanillaThemeKit } from '@quefep/theme-kit/vanilla';
const theme = new ThemeKit({ tokens });
const vanillaTheme = createVanillaThemeKit(theme);
document.body.style.backgroundColor = vanillaTheme.getColor('background');// Switch to dark mode
theme.switch('dark');
// Or pass custom tokens on the fly
theme.switch('custom', {
colors: {
primary: '#FF6B6B',
background: '#1A1A1A',
},
});
// Check what's active
console.log(theme.getCurrentTheme()); // 'dark'In React:
function ThemeToggle() {
const { currentTheme, switchTheme } = useTheme();
return (
<button onClick={() => switchTheme(currentTheme === 'light' ? 'dark' : 'light')}>
{currentTheme === 'light' ? 'Go dark' : 'Go light'}
</button>
);
}Reference other tokens using curly braces. ThemeKit v2.0.0 adds powerful new aliasing capabilities:
{
"colors": {
"blue500": "#4A90E2",
"primary": "{colors.blue500}",
"secondary": "{dark:colors.primary}",
"lighter": "{colors.primary + 20}",
"darker": "{colors.primary * 0.8}"
}
}New v2.0.0 Features:
- Lazy Resolution: Resolve aliases only when accessed for better performance
- Cross-Theme References: Use
{theme:path}to reference tokens from other themes - Runtime Expressions: Simple arithmetic operations in aliases
Enable enhanced runtime aliasing:
const theme = new ThemeKit({
tokens,
aliasing: {
enabled: true,
lazy: true, // Resolve on-demand (faster startup)
allowCrossTheme: true, // Reference other themes
allowExpressions: true, // Arithmetic operations
maxDepth: 15,
},
});ThemeKit now caches resolved tokens and lets you warm them up ahead of time:
theme.prefetchThemes(); // prefetch everything
theme.prefetchThemes(['dark']); // or target a subsetCombine this with the built-in cache to keep theme switches snappy even with massive token graphs.
Built-in functions for color manipulation:
import { lighten, darken, alpha, mix, contrastRatio, readableOn } from '@quefep/theme-kit';
lighten('#4A90E2', 20); // 20% lighter
darken('#4A90E2', 20); // 20% darker
alpha('#4A90E2', 0.5); // rgba with 50% opacity
mix('#4A90E2', '#FF6B6B', 50); // 50/50 blend
contrastRatio('#000', '#fff'); // WCAG contrast ratio
readableOn('#4A90E2'); // returns black or white for textGenerate token files in different formats:
# CSS variables
npx @quefep/theme-kit generate --format css
# SCSS with custom prefix
npx @quefep/theme-kit generate --format scss --prefix myapp
# TypeScript types
npx @quefep/theme-kit generate --format ts
# Resolve aliases before generating
npx @quefep/theme-kit generate --format css --resolve-aliases
# Validate your tokens
npx @quefep/theme-kit validate
# Compare two token files
npx @quefep/theme-kit diff tokens-v1.json tokens-v2.json
# Start a new project
npx @quefep/theme-kit init --template basicCSS output looks like:
:root {
--tk-colors-primary: #4A90E2;
--tk-colors-secondary: #50E3C2;
--tk-colors-background: #FFFFFF;
--tk-colors-text: #333333;
--tk-typography-fontFamily: Inter, sans-serif;
--tk-spacing-sm: 4px;
--tk-spacing-md: 8px;
--tk-spacing-lg: 16px;
}Drop a themekit.config.js in your project root:
module.exports = {
tokens: './tokens.json',
defaultTheme: 'light',
aliasing: {
enabled: true,
maxDepth: 10,
},
persistence: {
enabled: true,
key: 'themekit-theme',
storage: 'localStorage',
},
validation: {
enabled: true,
strict: false,
rules: [
{
name: 'naming-convention',
pattern: /^[a-z][a-zA-Z0-9-_]*$/,
message: 'Use camelCase or kebab-case for token names',
severity: 'warning',
},
{
name: 'color-format',
pattern: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$|^rgb\(|^rgba\(/,
message: 'Colors should be hex, rgb, or rgba',
severity: 'error',
},
],
},
};const theme = new ThemeKit(config);
theme.registerTheme({ name: 'dark', tokens: {...} });
theme.switch('dark');
theme.getCurrentTheme(); // 'dark'
theme.getCurrentTokens(); // { colors: {...}, ... }
theme.getToken('colors.primary');
theme.getAvailableThemes(); // ['light', 'dark']
theme.prefetchThemes(); // Warm up caches for one or more themes
theme.addThemeChangeListener((name, tokens) => { ... });
theme.validateTokens(tokens); // { valid: true, errors: [] }useTheme()— full theme contextuseToken(path)— grab a specific tokenuseColor(name)— shorthand for colorsuseTypography(name)— typography tokensuseSpacing(name)— spacing tokens
Same deal: useThemeKit(), useToken(), useColor(), useTypography(), useSpacing()
Inject ThemeKitService and call switchTheme(), getToken(), etc.
src/
├── core/ # The main ThemeKit class
├── types/ # TypeScript definitions
├── react/ # React bindings
├── vue/ # Vue bindings
├── angular/ # Angular bindings
├── vanilla/ # Vanilla JS adapter
└── cli/ # CLI commands
- Keep tokens atomic (e.g.,
blue-500) rather than semantic (e.g.,button-background) - Version control your token files
- Run validation in CI so bad tokens don't slip through
- Generate static CSS for production when you can
- Svelte and SolidJS adapters
- Figma/Sketch sync
- Multi-brand support
- Visual token editor
git clone https://github.com/M1tsumi/Theme-Kit.git
cd Theme-Kit
npm install
npm run devRun tests with npm test, build with npm run build.
MIT