Integrations

Storybook

Integrate Styleframe with Storybook to showcase your design system with type-safe recipes, design tokens, and dark mode support.

Overview

Styleframe integrates with Storybook so you can develop, document, and test your design system components in isolation. The Styleframe Vite plugin handles all the wiring — your design tokens, utility classes, and recipe functions are available in every story automatically.

Key features:

  • Zero-config styling: Import virtual:styleframe.css once and all tokens, utilities, and recipes are available
  • Type-safe recipes: Component stories get full autocompletion for recipe variant props
  • Co-located styles: Each component's *.styleframe.ts file is discovered and compiled automatically
  • Dark mode support: Toggle themes in the Storybook UI with data-theme attributes
  • Framework-agnostic tokens: The same styleframe.config.ts works with Vue, React, or vanilla HTML stories

Prerequisites

Before you begin, ensure you have:

  • A Styleframe project with design tokens defined (Installation Guide)
  • Node.js 18+
  • Storybook 10+

Installation

Install Storybook and the framework adapter for your project:

pnpm add -D storybook @storybook/react-vite
If you already have Storybook set up, you only need to follow the steps below to add Styleframe support. The framework adapter should already be installed.

Setup

Create a Styleframe config

If you don't have one yet, create a styleframe.config.ts at your project root. This is the single source of truth for your design system.

styleframe.config.ts
import {
    useDesignTokensPreset,
    useGlobalPreset,
    useModifiersPreset,
    useSanitizePreset,
    useUtilitiesPreset,
} from '@styleframe/theme';
import { styleframe } from 'styleframe';

const s = styleframe();

useDesignTokensPreset(s);
useSanitizePreset(s);
useGlobalPreset(s);
useUtilitiesPreset(s);
useModifiersPreset(s);

export default s;

Configure Vite with the Styleframe plugin

Add the Styleframe Vite plugin to your vite.config.ts. The scanner.content option tells the plugin which files to scan for utility class usage.

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import styleframe from 'styleframe/plugin/vite';

export default defineConfig({
    plugins: [
        styleframe({
            scanner: {
                content: [
                    './stories/**/*.stories.{ts,tsx}',
                    './src/**/*.{ts,tsx}',
                ],
            },
        }),
        react(),
    ],
});

Configure Storybook

Create or update .storybook/main.ts to point at your story files and set the framework adapter:

.storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
    stories: [
        '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)',
    ],
    addons: [
        '@storybook/addon-docs',
    ],
    framework: '@storybook/react-vite',
};

export default config;

Import styles in the preview

Create .storybook/preview.ts and import the compiled CSS. This single import makes all your design tokens, utility classes, and recipe styles available to every story.

.storybook/preview.ts
import type { Preview } from '@storybook/react-vite';

import 'virtual:styleframe.css';

const preview: Preview = {
    parameters: {
        layout: 'centered',
    },
};

export default preview;
Pro tip: Run storybook dev to start the dev server. The Styleframe plugin compiles your config and extension files on the fly, with hot module replacement for instant feedback.

Writing Stories

Stories follow a three-file pattern: a *.styleframe.ts file to register the recipe, a component file to consume it, and a *.stories.ts file for Storybook.

Register a recipe

Create a *.styleframe.ts file alongside your story. The Vite plugin discovers it automatically and makes the recipe available via virtual:styleframe.

stories/button.styleframe.ts
import { useButtonRecipe } from '@styleframe/theme';
import { styleframe } from 'virtual:styleframe';

const s = styleframe();

const button = useButtonRecipe(s);

export default s;

Build the component

Import the button runtime function from virtual:styleframe and pass variant props to compute class names. The function is fully typed — invalid colors, variants, or sizes are caught at compile time.

src/components/Button.tsx
import { button } from 'virtual:styleframe';

interface ButtonProps {
    color?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'light' | 'dark' | 'neutral';
    variant?: 'solid' | 'outline' | 'soft' | 'subtle' | 'ghost' | 'link';
    size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
    label?: string;
    disabled?: boolean;
}

export function Button({
    color = 'neutral',
    variant = 'solid',
    size = 'md',
    label = 'Button',
    disabled = false,
}: ButtonProps) {
    return (
        <button className={button({ color, variant, size })} disabled={disabled}>
            {label}
        </button>
    );
}

Write the story

Create a *.stories.ts file using Storybook's Component Story Format (CSF):

stories/button.stories.ts
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from '../src/components/Button';

const meta = {
    title: 'Components/Button',
    component: Button,
    tags: ['autodocs'],
    argTypes: {
        color: {
            control: 'select',
            options: ['primary', 'secondary', 'success', 'info', 'warning', 'error'],
        },
        variant: {
            control: 'select',
            options: ['solid', 'outline', 'soft', 'subtle', 'ghost', 'link'],
        },
        size: {
            control: 'select',
            options: ['xs', 'sm', 'md', 'lg', 'xl'],
        },
        label: { control: 'text' },
        disabled: { control: 'boolean' },
    },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
    args: {
        label: 'Button',
    },
};

export const Primary: Story = {
    args: {
        color: 'primary',
        label: 'Primary',
    },
};

export const Outline: Story = {
    args: {
        variant: 'outline',
        label: 'Outline',
    },
};
The tags: ['autodocs'] option tells Storybook to auto-generate a documentation page for the component from its stories and prop types.

Dark Mode

Styleframe themes are driven by the data-theme attribute on the DOM. To toggle dark mode in Storybook, listen for a theme change event in your preview and update the attribute:

.storybook/preview.ts
import 'virtual:styleframe.css';

const channel = (await import('storybook/preview-api')).addons.getChannel();
const { DARK_MODE_EVENT_NAME } = await import('@vueless/storybook-dark-mode');

channel.on(DARK_MODE_EVENT_NAME, (isDark: boolean) => {
    if (isDark) {
        document.body.dataset.theme = 'dark';
    } else {
        delete document.body.dataset.theme;
    }
});

This requires the @vueless/storybook-dark-mode addon. Install it and add it to your .storybook/main.ts addons array:

pnpm add -D @vueless/storybook-dark-mode
.storybook/main.ts
const config = {
    // ...
    addons: [
        '@storybook/addon-docs',
        '@vueless/storybook-dark-mode',
    ],
};

Once configured, a sun/moon toggle appears in the Storybook toolbar. Every recipe and utility that references theme-aware tokens (like @color.background or @color.text) updates automatically.

Pro tip: The dark mode addon works with any framework — Vue, React, or vanilla HTML. It toggles a class and fires an event; the Styleframe integration only needs the data-theme attribute on the body.

Best Practices

  • Keep one styleframe.config.ts: Share the same config between your app and Storybook so stories reflect the real design system
  • Co-locate *.styleframe.ts files: Place recipe registrations next to the stories or components that use them for easy discovery
  • Use tags: ['autodocs']: Let Storybook auto-generate docs pages from your component props and stories
  • Scan story files: Include ./stories/**/*.stories.{ts,tsx} in the scanner content so utility classes used in stories are extracted
  • Showcase all variants: Create stories for each meaningful combination of recipe props — colors, variants, sizes, and states
  • Test dark mode: Use the dark mode toggle to verify every component looks correct in both themes

FAQ