API Essentials

Imports

Learn the two ways to import Styleframe styles into your application—global imports for centralized design systems and per-file imports for component-scoped styling.

Overview

Styleframe provides two patterns for importing styles into your application:

  1. Global imports: A centralized styleframe.config.ts file that generates styles for your entire application
  2. Per-file imports: Individual *.styleframe.ts files co-located with your components

Both approaches use the same Styleframe API and can be combined in a single project. This guide helps you understand when to use each pattern and how they work together.

Global Imports

Global imports use a single styleframe.config.ts configuration file at your project root. All styles are compiled together and imported via virtual modules.

Setup

styleframe.config.ts
import { styleframe } from 'styleframe';

const s = styleframe();
const { variable, utility, recipe, ref } = s;

// Design tokens
const colorPrimary = variable('color.primary', '#3b82f6');
const colorSecondary = variable('color.secondary', '#64748b');
const spacingMd = variable('spacing.md', '1rem');

// Utilities
utility('background', ({ value }) => ({ backgroundColor: value }));
utility('padding', ({ value }) => ({ padding: value }));

// Recipes
recipe({
    name: 'button',
    base: { padding: ref(spacingMd) },
    variants: {
        color: {
            primary: { background: ref(colorPrimary) },
            secondary: { background: ref(colorSecondary) },
        },
    },
});

export default s;

When to use global imports

  • Building a centralized design system with shared tokens across your application
  • Defining global styles that apply everywhere
  • Working with a smaller application where code splitting isn't critical
  • Preferring a single source of truth for all styling decisions

Per-File Imports

Per-file imports use individual *.styleframe.ts files placed alongside your components. Each file is compiled independently and imported directly.

Setup

src/components/button.styleframe.ts
import { styleframe } from 'styleframe';

const s = styleframe();
const { variable, utility, recipe, ref } = s;

// Component-specific tokens
const buttonPrimary = variable('button.primary', '#3b82f6');
const buttonSecondary = variable('button.secondary', '#64748b');
const buttonPadding = variable('button.padding', '1rem');

// Component utilities
utility('background', ({ value }) => ({ backgroundColor: value }));
utility('padding', ({ value }) => ({ padding: value }));

// Component recipe
recipe({
    name: 'button',
    base: { padding: ref(buttonPadding) },
    variants: {
        color: {
            primary: { background: ref(buttonPrimary) },
            secondary: { background: ref(buttonSecondary) },
        },
    },
});

export default s;

When to use per-file imports

  • Building a component library with self-contained components
  • Working on a large application with independent feature modules
  • Preferring co-located styles next to components
  • Wanting automatic code splitting for styles

Avoid code duplication with shared modules

Each .styleframe.ts file is compiled independently. If multiple files import a shared module that registers variables or other styles, that code will be re-executed for each file, potentially causing duplicate CSS output.

To avoid this:

  • Define shared tokens in your global styleframe.config.ts instead
  • Keep .styleframe.ts files self-contained without shared registration functions
For example, if you create a useTokens() function that calls variable() and import it in multiple .styleframe.ts files, each file will register those variables separately.

Comparison

AspectGlobal ImportsPer-File Imports
ConfigurationSingle styleframe.config.tsMultiple .styleframe.ts files
Import pathvirtual:styleframe.css./component.styleframe?css
ScopeApplication-wideComponent-level
Code splittingSingle bundleAutomatic per-component
Token sharingBuilt-inRequires shared modules
Best forDesign systems, shared tokensComponent libraries, isolation

Combining Both Patterns

You can use both patterns in the same project. A common approach is to define shared tokens and utilities globally, while keeping component-specific recipes in per-file imports:

src/main.ts
// Global design tokens and base utilities
import 'virtual:styleframe.css';
src/components/Button.tsx
// Component-specific styles and recipes
import './button.styleframe?css';
import { button } from './button.styleframe?ts';

export function Button({ variant, children }) {
    return (
        <button className={button({ variant })}>
            {children}
        </button>
    );
}
When combining both patterns, be mindful of potential naming conflicts between utilities or variables defined in different files.

Import Types

Both patterns support two types of imports:

CSS Import

Imports the compiled CSS containing variables, selectors, utilities, and recipe class definitions.

// Global
import 'virtual:styleframe.css';

// Per-file
import './component.styleframe?css';

TypeScript Import

Imports compiled TypeScript exports from your Styleframe files. This includes:

  • Recipe functions for runtime variant selection
  • Selector strings for programmatic DOM access
// Global
import { button, card } from 'virtual:styleframe';

// Per-file
import { button, buttonSelector } from './button.styleframe?ts';
If you're not using recipes or selector exports, you only need the CSS import. The TypeScript import is for runtime functions and constants that you need to reference in your JavaScript/TypeScript code.

Named Exports

By default, recipes are exported using a name derived from the recipe's name property. However, you can control the export name by using a named export in your Styleframe file.

Recipe Exports

When you assign a recipe to a named export, that exact name is preserved in the generated TypeScript:

button.styleframe.ts
import { styleframe } from 'styleframe';

const s = styleframe();

// Named export - uses your chosen name
export const primaryButton = s.recipe({
    name: 'button',
    base: { cursor: 'pointer' },
    variants: {
        size: {
            sm: { padding: '0.5rem' },
            lg: { padding: '1rem' },
        },
    },
});

export default s;

Without a named export, the export name is derived from the recipe's name property (converted to camelCase):

// Without named export
s.recipe({ name: 'primary-button', /* ... */ });

// Generated export name: primaryButton (derived from recipe name)

Selector Exports

You can also export selectors as string constants. This is useful when you need to reference a selector in your JavaScript code—for example, with DOM APIs or testing utilities.

card.styleframe.ts
import { styleframe } from 'styleframe';

const s = styleframe();

// Named selector export
export const cardSelector = s.selector('.card', {
    padding: '1rem',
    borderRadius: '8px',
    boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
});

export const cardHeaderSelector = s.selector('.card-header', {
    fontWeight: 'bold',
    marginBottom: '0.5rem',
});

export default s;
Only selectors with named exports are included in the ?ts output. Selectors without named exports generate CSS but no TypeScript export.

Combining Recipes and Selectors

A common pattern is to export both recipes (for variant-based styling) and selectors (for static styles or DOM access):

button.styleframe.ts
import { styleframe } from 'styleframe';

const s = styleframe();
const { variable, ref } = s;

const buttonPadding = variable('button.padding', '1rem');

// Static base styles via selector
export const buttonBase = s.selector('.btn', {
    display: 'inline-flex',
    alignItems: 'center',
    borderRadius: '4px',
});

// Variant styles via recipe
export const button = s.recipe({
    name: 'button',
    base: { padding: ref(buttonPadding) },
    variants: {
        variant: {
            primary: { background: 'blue', color: 'white' },
            secondary: { background: 'gray', color: 'black' },
        },
    },
});

export default s;
Button.tsx
import './button.styleframe?css';
import { buttonBase, button } from './button.styleframe?ts';

export function Button({ variant, children }) {
    // Combine base selector class with recipe variant classes
    return (
        <button className={`${buttonBase.slice(1)} ${button({ variant })}`}>
            {children}
        </button>
    );
}

Sharing Tokens Between Files

When using per-file imports, you may want to share tokens across components. Extract shared values into a regular TypeScript file:

src/tokens.ts
// Shared design tokens (regular TS file, not a .styleframe.ts)
export const colors = {
    primary: '#3b82f6',
    secondary: '#64748b',
    white: '#ffffff',
};

export const spacing = {
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
};
src/components/button.styleframe.ts
import { styleframe } from 'styleframe';
import { colors, spacing } from '../tokens';

const s = styleframe();
const { variable } = s;

// Use shared tokens
const buttonPrimary = variable('button.primary', colors.primary);
const buttonPadding = variable('button.padding', spacing.md);

// ... rest of component styles
export default s;

Framework Usage Examples

import { useMemo } from 'react';
import './button.styleframe?css';
import { button } from './button.styleframe?ts';

interface ButtonProps {
    variant?: 'primary' | 'secondary';
    children: React.ReactNode;
}

export function Button({ variant, children }: ButtonProps) {
    const className = useMemo(() => button({ variant }), [variant]);

    return (
        <button className={className}>
            {children}
        </button>
    );
}

FAQ