Themes
Overview
Themes in Styleframe allow you to create systematic variations of your design system by overriding variables, selectors, and utilities in specific contexts.
They provide a powerful way to implement dark mode, brand themes, seasonal variations, or user personalization while maintaining full type safety and consistency across your application.
Why use themes?
Themes help you:
- Create consistent design variations: Implement dark mode, brand themes, or seasonal variations with centralized variable overrides.
- Enable user personalization: Allow users to customize their experience while maintaining design system consistency.
- Maintain type safety: Get full TypeScript support for theme variable and selector overrides, preventing invalid configurations.
- Scale theming systems: Build complex theming scenarios with nested contexts and conditional overrides.
Defining Themes
You define a theme using the theme() function from your styleframe instance:
import { styleframe } from 'styleframe';
const s = styleframe();
const { theme, variable, ref, selector } = s;
const cardBackground = variable('card--background', '#ffffff');
const cardColor = variable('card--color', '#000000');
selector('.card', {
background: ref(cardBackground),
color: ref(cardColor),
});
theme('dark', (ctx) => {
ctx.variable(cardBackground, '#18181b');
ctx.variable(cardColor, '#ffffff');
});
export default s;
:root {
--card--background: #ffffff;
--card--color: #000000;
}
.card {
background: var(--card--background);
color: var(--card--color);
}
[data-theme="dark"] {
--card--background: #18181b;
--card--color: #ffffff;
}
Each theme takes a theme name and a callback function that receives a context object with theming utilities.
default, dark, brand-primary, high-contrast). This makes your theming system more maintainable.Theme Context
Overriding Variables
You can override variables that use references within the theme context:
import { styleframe } from 'styleframe';
const s = styleframe();
const { theme, variable, ref, selector } = s;
const colorWhite = variable('color--white', '#ffffff');
const colorBlack = variable('color--black', '#000000');
const buttonColor = variable('button--color', ref(colorBlack));
selector('.button', {
background: '#3b82f6',
color: ref(buttonColor),
});
theme('dark', (ctx) => {
ctx.variable(buttonColor, ref(colorWhite));
});
export default s;
:root {
--color--white: #ffffff;
--color--black: #000000;
--button--color: var(--color--black);
}
.button {
background: #3b82f6;
color: var(--button--color);
}
[data-theme="dark"] {
--button--color: var(--color--white);
}
button--color variable is overridden in the dark theme context to use color--white. This allows the button text color to change based on the active theme.Overriding Selectors
You can also override selectors within a theme context:
import { styleframe } from 'styleframe';
const s = styleframe();
const { theme, selector } = s;
const card = selector('.card', {
background: '#f8fafc',
color: '#1f2937',
});
theme('dark', (ctx) => {
ctx.selector('.card', {
background: '#1f2937',
});
});
export default s;
.card {
background: #f8fafc;
color: #1f2937;
}
[data-theme="dark"] {
.card {
background: #1f2937;
}
}
Applying Themes
Themes are applied by adding the appropriate data-theme attribute to HTML elements:
HTML Implementation
Apply themes using data attributes:
<!-- Default theme (no attribute needed) -->
<div class="card">Default theme content</div>
<!-- Dark theme -->
<div class="card" data-theme="dark">Dark theme content</div>
<!-- Brand theme -->
<div class="card" data-theme="brand-accent">Brand theme content</div>
<!-- Theme applied to entire page -->
<html data-theme="dark">
<body>
<div class="card">All content uses dark theme</div>
</body>
</html>
Theme Switching Implementation
To switch themes dynamically, you can use JavaScript to toggle the data-theme attribute on the <html> element:
import { useState, useEffect, useCallback } from 'react';
type Theme = 'light' | 'dark';
export const useTheme = () => {
const [theme, setThemeState] = useState<Theme>('light');
const setTheme = useCallback((newTheme: Theme) => {
setThemeState(newTheme);
if (typeof window !== 'undefined') {
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
}, []);
const toggleTheme = useCallback(() => {
setTheme(theme === 'dark' ? 'light' : 'dark');
}, [theme, setTheme]);
useEffect(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('theme') as Theme;
if (saved && (saved === 'light' || saved === 'dark')) {
setTheme(saved);
}
}
}, [setTheme]);
return {
theme,
setTheme,
toggleTheme
} as const;
};
import { ref, onMounted, readonly } from 'vue';
type Theme = 'light' | 'dark';
export const useTheme = () => {
const theme = ref<Theme>('light');
const setTheme = (newTheme: string) => {
theme.value = newTheme;
if (typeof window !== 'undefined') {
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
}
const toggleTheme = () => {
setTheme(theme.value === 'dark' ? 'light' : 'dark')
}
onMounted(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('theme') as Theme;
if (saved) {
setTheme(saved);
}
}
});
return {
theme: readonly(theme),
setTheme,
toggleTheme
}
}
type Theme = 'light' | 'dark';
let currentTheme: Theme = 'light';
const initializeTheme = (): void => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('theme') as Theme;
if (saved) {
currentTheme = saved;
}
document.documentElement.setAttribute('data-theme', currentTheme);
}
};
export const getTheme = (): Theme => currentTheme;
export const setTheme = (newTheme: Theme): void => {
currentTheme = newTheme;
if (typeof window !== 'undefined') {
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
};
export const toggleTheme = (): void => {
setTheme(currentTheme === 'dark' ? 'light' : 'dark');
};
if (typeof window !== 'undefined') {
initializeTheme();
}
Examples
Multiple Brand Themes
Create multiple brand themes for different contexts:
import { styleframe } from 'styleframe';
const s = styleframe();
const { theme, variable, ref } = s;
const brandPrimary = variable('brand--primary', '#3b82f6');
const brandSecondary = variable('brand--secondary', '#64748b');
const brandAccent = variable('brand--accent', '#f59e0b');
theme('brand-healthcare', (ctx) => {
ctx.variable(brandPrimary, '#059669');
ctx.variable(brandSecondary, '#10b981');
ctx.variable(brandAccent, '#34d399');
});
theme('brand-finance', (ctx) => {
ctx.variable(brandPrimary, '#1e40af');
ctx.variable(brandSecondary, '#3b82f6');
ctx.variable(brandAccent, '#60a5fa');
});
theme('brand-creative', (ctx) => {
ctx.variable(brandPrimary, '#7c3aed');
ctx.variable(brandSecondary, '#a855f7');
ctx.variable(brandAccent, '#c084fc');
});
export default s;
Best Practices
- Plan your theme architecture: Define your base variables thoughtfully and consider how they'll be overridden in different themes.
- Use semantic naming: Name variables based on their purpose (
color--text) rather than their appearance. - Test theme combinations: Ensure all themes work well with your components and maintain proper contrast ratios.
- Consider accessibility: Ensure sufficient color contrast and readable typography in all theme variations.
- Group related overrides: Use composables to organize theme definitions and make them reusable across projects.
FAQ
data-theme attribute is supported per element at a time.transition properties will animate smoothly when theme values change.localStorage or cookies, then apply it on page load. Many frameworks provide built-in theme persistence utilities.Utilities
Styleframe utilities provide reusable, single-purpose CSS classes with full type safety and recipe support. Build consistent, maintainable styling systems with atomic design patterns.
Recipes 🚧
Styleframe recipes provide powerful component variants with type-safe configuration options. Create flexible, reusable UI components with consistent styling patterns and runtime variant selection.