Imports
Overview
Styleframe uses a single-instance architecture with three layers:
styleframe.config.ts— Creates the global Styleframe instance with your design tokens, presets, and utilities*.styleframe.tsfiles — Extend the global instance with component-specific selectors, recipes, and styles- Application code — Consumes the compiled CSS and TypeScript exports via
virtual:styleframe
*.styleframe.ts files share the same Styleframe instance created by your config. There are no independent instances — every file builds on the same foundation.The Global Config
The styleframe.config.ts file at your project root creates the single Styleframe instance that powers your entire design system.
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 spacingSm = variable('spacing.sm', '0.5rem');
const spacingMd = variable('spacing.md', '1rem');
const spacingLg = variable('spacing.lg', '1.5rem');
// 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) },
},
size: {
sm: { padding: ref(spacingSm) },
md: { padding: ref(spacingMd) },
lg: { padding: ref(spacingLg) },
},
},
});
export default s;
// Import all generated CSS
import 'virtual:styleframe.css';
// Import recipe functions (if using recipes)
import { button } from 'virtual:styleframe';
The config file:
- Imports
styleframefrom the"styleframe"package (the real npm package) - Creates the instance with
styleframe() - Defines tokens, utilities, recipes, and styles
- Must export the instance as default (
export default s)
Extension Files
Extension files (*.styleframe.ts) add component-specific styles to the global instance. They are co-located with your components and loaded automatically by the plugin.
import { styleframe } from 'virtual:styleframe';
const s = styleframe();
const { variable, 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 recipe
export const buttonRecipe = recipe({
name: 'button',
base: { padding: ref(buttonPadding) },
variants: {
color: {
primary: { background: ref(buttonPrimary) },
secondary: { background: ref(buttonSecondary) },
},
size: {
sm: { padding: ref('spacing.sm') },
md: { padding: ref('spacing.md') },
lg: { padding: ref('spacing.lg') },
},
},
});
export default s;
When an extension file calls styleframe(), the plugin returns the same instance from your config:
// What the plugin resolves "virtual:styleframe" to inside *.styleframe.ts files:
import config from './styleframe.config.ts';
export function styleframe() {
return config; // The same global instance
}
Every call to variable(), selector(), recipe(), etc. adds to the shared instance. All tokens defined in the config — and in previously loaded extensions — are available via ref().
import { buttonRecipe } from 'virtual:styleframe';
export function Button({ color, children }) {
return (
<button className={buttonRecipe({ color })}>
{children}
</button>
);
}
"virtual:styleframe", not "styleframe". Using the real package would create a separate, disconnected instance that is not part of your design system.ref().Consuming Styles
Application code consumes the compiled output through two imports:
| Import | What it provides |
|---|---|
import 'virtual:styleframe.css' | All compiled CSS — variables, selectors, utilities, and recipe classes |
import { ... } from 'virtual:styleframe' | Runtime exports — recipe functions and selector strings |
// 1. Import all compiled CSS (typically once, at your app entry point)
import 'virtual:styleframe.css';
// 2. Import recipe functions and selector strings where needed
import { buttonRecipe, alertRecipe, cardSelector } from 'virtual:styleframe';
// Use recipes to generate class names at runtime
const className = buttonRecipe({ color: 'primary', size: 'md' });
// Use selector strings for DOM access
const cards = document.querySelectorAll(cardSelector);
"virtual:styleframe" import path works differently depending on who imports it. Extension files (*.styleframe.ts) receive the raw Styleframe instance, while application code receives the compiled recipe functions and selector strings.Named Exports
Named exports from extension files control what's available to your application code through "virtual:styleframe".
Recipe Exports
When you assign a recipe to a named export, that name is preserved in the generated output:
import { styleframe } from 'virtual: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 output preserves your export name
export const primaryButton = createRecipe("button", { /* runtime */ });
import { primaryButton } from 'virtual:styleframe';
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 export selectors as string constants for programmatic DOM access or testing:
import { styleframe } from 'virtual:styleframe';
const s = styleframe();
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 output exports the selector query string
export const cardSelector = ".card";
export const cardHeaderSelector = ".card-header";
import { cardSelector, cardHeaderSelector } from 'virtual:styleframe';
// Use with DOM APIs
const cards = document.querySelectorAll(cardSelector);
// Use in tests
expect(wrapper.find(cardHeaderSelector)).toBeVisible();
"virtual:styleframe" TypeScript output — without a named export, they generate CSS only. Recipes are always included, using either the named export name or an auto-derived name from the recipe's name property. The export default s is required in your config file but optional in extension files.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 'virtual: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 { buttonBase, button } from 'virtual:styleframe';
export function Button({ variant, children }) {
// Combine base selector class with recipe variant classes
return (
<button className={`${buttonBase.slice(1)} ${button({ variant })}`}>
{children}
</button>
);
}
Framework Usage
import { useMemo } from 'react';
import { button } from 'virtual:styleframe';
interface ButtonProps {
color?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
export function Button({ color, size, children }: ButtonProps) {
const className = useMemo(() => button({ color, size }), [color, size]);
return (
<button className={className}>
{children}
</button>
);
}
<script setup lang="ts">
import { computed } from 'vue';
import { button } from 'virtual:styleframe';
const props = defineProps<{
color?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
}>();
const className = computed(() => button({ color: props.color, size: props.size }));
</script>
<template>
<button :class="className">
<slot />
</button>
</template>
<script lang="ts">
import { button } from 'virtual:styleframe';
export let color: 'primary' | 'secondary' = 'primary';
export let size: 'sm' | 'md' | 'lg' = 'md';
$: className = button({ color, size });
</script>
<button class={className}>
<slot />
</button>
'virtual:styleframe.css' once at your application entry point (e.g., main.ts or app.ts). You do not need to import it in every component file.FAQ
:::accordion-item{label="Why do extension files import from "virtual:styleframe" instead of "styleframe"?" icon="i-lucide-circle-help"}
The "virtual:styleframe" import is resolved by the plugin to return the global instance created by your styleframe.config.ts. Importing from "styleframe" (the npm package) would call styleframe() to create a new, disconnected instance — your extension's styles would not be part of the compiled output.
:::
ref().*.styleframe.ts files are loaded in alphabetical order. You can configure this with the loadOrder option in the plugin configuration.:::accordion-item{label="Does every extension file need "export default s"?" icon="i-lucide-circle-help"}
Yes. The default export is how the plugin identifies the file as a Styleframe extension. Named exports (recipes, selectors) are what become available to your application code via "virtual:styleframe".
:::
*.styleframe.ts file or the config changes, the plugin reloads the config and all extension files to rebuild the global instance. CSS changes are hot-reloaded without a full page refresh, and TypeScript changes trigger component re-renders.Yes. The plugin works with any bundler supported by @styleframe/plugin:
- Vite
- Nuxt
- webpack
- Rollup
- esbuild
The plugin handles virtual module resolution and compilation for all supported bundlers.
Merging
Combine multiple Styleframe instances into a single unified configuration. Perfect for composing design systems, sharing configurations across projects, or building modular styling architectures.
Overview
Explore the tools that integrate Styleframe into your development workflow — from automatic CSS generation at build time to bidirectional design token syncing with Figma.