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
Styleframe themes use CSS cascade, so you can nest theme contexts. Inner themes will override outer ones. However, only one data-theme
attribute is supported per element at a time.
Use theme-specific variable overrides to handle variations. You can also combine themes with selectors that target the theme attribute directly for more complex scenarios.
Yes! Add CSS transitions to the properties that change between themes. Variables that use transition
properties will animate smoothly when theme values change.
Store the theme preference in localStorage
or cookies
, then apply it on page load. Many frameworks provide built-in theme persistence utilities.
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.
Composables
Styleframe composables are reusable functions that provide design system components like variables, selectors, and utilities. They enable modular, consistent styling patterns across your application.