API Essentials

Themes

Styleframe themes provide powerful design system theming capabilities with type-safe variable overrides. Create consistent light/dark modes, brand variants, and user personalization with ease.

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;

Each theme takes a theme name and a callback function that receives a context object with theming utilities.

Pro tip: Use descriptive theme names that clearly indicate their purpose (e.g., 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;
Good to know: In the example above, the 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;

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; 
};

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