Imports
Overview
Styleframe provides two patterns for importing styles into your application:
- Global imports: A centralized
styleframe.config.tsfile that generates styles for your entire application - Per-file imports: Individual
*.styleframe.tsfiles 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
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;
// Import all generated CSS
import 'virtual:styleframe.css';
// Import recipe functions (if using recipes)
import { button } from 'virtual:styleframe';
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
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;
// Import compiled CSS
import './button.styleframe?css';
// Import recipe functions
import { button } from './button.styleframe?ts';
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.tsinstead - Keep
.styleframe.tsfiles self-contained without shared registration functions
useTokens() function that calls variable() and import it in multiple .styleframe.ts files, each file will register those variables separately.Comparison
| Aspect | Global Imports | Per-File Imports |
|---|---|---|
| Configuration | Single styleframe.config.ts | Multiple .styleframe.ts files |
| Import path | virtual:styleframe.css | ./component.styleframe?css |
| Scope | Application-wide | Component-level |
| Code splitting | Single bundle | Automatic per-component |
| Token sharing | Built-in | Requires shared modules |
| Best for | Design systems, shared tokens | Component 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:
// Global design tokens and base utilities
import 'virtual:styleframe.css';
// 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>
);
}
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';
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:
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;
// Generated ?ts output preserves your export name
export const primaryButton = createRecipe("button", { /* runtime */ });
import './button.styleframe?css';
import { primaryButton } from './button.styleframe?ts';
// Use your named export
const className = primaryButton({ size: 'lg' });
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.
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;
// Generated ?ts output exports the selector query string
export const cardSelector = ".card";
export const cardHeaderSelector = ".card-header";
import './card.styleframe?css';
import { cardSelector, cardHeaderSelector } from './card.styleframe?ts';
// Use with DOM APIs
const cards = document.querySelectorAll(cardSelector);
// Use in tests
expect(wrapper.find(cardHeaderSelector)).toBeVisible();
// Use with dynamic element creation
const card = document.createElement('div');
card.className = cardSelector.slice(1); // Remove the leading '.'
?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):
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;
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:
// 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',
};
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>
);
}
<script setup lang="ts">
import { computed } from 'vue';
import './button.styleframe?css';
import { button } from './button.styleframe?ts';
const props = defineProps<{
variant?: 'primary' | 'secondary';
}>();
const className = computed(() => button({ variant: props.variant }));
</script>
<template>
<button :class="className">
<slot />
</button>
</template>
<script lang="ts">
import './button.styleframe?css';
import { button } from './button.styleframe?ts';
export let variant: 'primary' | 'secondary' = 'primary';
$: className = button({ variant });
</script>
<button class={className}>
<slot />
</button>
FAQ
@styleframe/plugin automatically handles .styleframe.ts files. Just create your file and import it with either ?css (for styles) or ?ts (for TypeScript exports like recipes and selectors).Both patterns support HMR in development. When you modify any Styleframe file:
- CSS changes are hot-reloaded without a full page refresh
- TypeScript changes (recipes) trigger component re-renders
This provides instant feedback on style changes regardless of which pattern you use.
Global imports bundle all styles together, which is efficient for smaller applications but includes unused styles.
Per-file imports enable automatic code splitting—only the styles for imported components are included. This can reduce initial bundle size for larger applications.
Both patterns use the tree-shakeable @styleframe/runtime package for recipe functions.
Yes. Both patterns work with any build tool supported by @styleframe/plugin:
- Vite
- Nuxt
- webpack
- Rollup
- esbuild
The plugin handles virtual module resolution and compilation for all supported bundlers.