Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
Styleframe is a type-safe, composable CSS-in-TypeScript framework for building design systems. It compiles design tokens, utility classes, component recipes, and themes into CSS, TypeScript runtime code, and type declarations.
styleframe.config.ts → Global Styleframe instance (single source of truth)
*.styleframe.ts files → Extend the global instance (tokens, recipes, utilities)
@styleframe/plugin → Build tool integration (Vite, Webpack, Nuxt, Astro, etc.)
@styleframe/transpiler → Compiles AST to CSS + TypeScript + .d.ts
@styleframe/runtime → Lightweight browser runtime for recipe class generation (~1.4KB)
virtual:styleframe → Auto-generated TypeScript with recipe functions and selectors
virtual:styleframe.css → Auto-generated CSS with all styles
All *.styleframe.ts extension files share one Styleframe instance from styleframe.config.ts. Extension files import the instance from virtual:styleframe, add tokens/recipes/utilities, and export it as default. Application code imports compiled recipe functions and selectors from virtual:styleframe.
Engine packages (engine/):
core/— @styleframe/core — Token AST, factory methods (variable, ref, selector, utility, modifier, recipe, theme, css, keyframes, media, atRule, merge)loader/— @styleframe/loader — Runtime config loading, module loading, HMR, buildruntime/— @styleframe/runtime — Browser-side recipe class name generationscanner/— @styleframe/scanner — Content scanning for utility class extractionstyleframe/— styleframe — Barrel package re-exporting all engine APIstranspiler/— @styleframe/transpiler — AST-to-CSS/TS/DTS code generation
Theme (theme/):
- @styleframe/theme — Design token composables, modifiers, utilities, recipes, presets
Tooling (tooling/):
cli/— @styleframe/cli — CLI for init, build, and Figma syncfigma/— @styleframe/figma — Bidirectional Figma variable sync via DTCG formatplugin/— @styleframe/plugin — Unplugin build integration (Vite, Webpack, Nuxt, Astro, Rollup, Rspack, esbuild, Farm)
Config (config/):
- @styleframe/config-typescript, @styleframe/config-vite — Shared build configs
Applications (apps/):
docs/— Documentation site (Nuxt Content + Markdown)app/— Customer dashboard (Nuxt 3 + Supabase)shared/— Shared Nuxt layer for doc appsstorybook/— Storybook 10 + Vue 3 design system showcase
Testing (testing/):
integration/— Playwright end-to-end tests across Chromium, Firefox, WebKit
Import rule: Import from 'styleframe' (the barrel package), not @styleframe/* sub-packages. Use 'styleframe/plugin/vite' for plugins, 'styleframe/loader' for loader, 'styleframe/transpiler' for transpiler.
ALWAYS create a Styleframe instance and destructure the methods:
import { styleframe } from 'styleframe';
const s = styleframe();
const { variable, ref, selector, utility, modifier, recipe, theme, atRule, keyframes, media, css } = s;
export default s;- Use dot notation:
color.primarybecomes CSS--color--primary - ALWAYS use
ref()to reference variables in declarations - For string references, use the
@prefix syntax:"@spacing.md","@color.primary","@font-size.sm" - Use
{ default: true }for variables in reusable composables - NEVER hardcode values that should be tokens
- Generated format:
_property-name:value(e.g.,_margin:md) - Modifier prefix format:
_modifier:property:value(e.g.,_hover:background:primary) - Boolean true omits value:
_display - Multiple modifiers chain:
_dark:hover:background:primary
- Default:
[data-theme="name"] - Apply themes via
data-themeattribute on HTML elements
styleframe.config.ts— Global config at project root. MUST export instance as default.*.styleframe.ts— Extension files that extend the global instance. Import fromvirtual:styleframe..styleframe/— Auto-generated type declarations directory. Add to.gitignore.
Creates a CSS custom property. Dot notation in name becomes -- in CSS.
const colorPrimary = variable('color.primary', '#006cff');
const spacing = variable('spacing', '1rem', { default: true }); // For composables
const colorAccent = variable('color.accent', ref(colorPrimary)); // Reference another variableCreates a var(--name) reference. Accepts a Variable token or dot-notation string.
backgroundColor: ref(colorPrimary)
color: ref('color.text', '#000') // String ref with fallback
color: "@color.primary" // @-prefixed string ref (shorthand)Creates a CSS selector rule. Supports nested selectors (&:hover), nested @media, and callback form.
selector('.button', {
padding: ref(spacing),
'&:hover': { opacity: 0.9 },
'@media (min-width: 768px)': { padding: '2rem' },
});
// Callback form for complex nesting
selector('.container', ({ selector, media }) => {
selector('.inner', { padding: '1rem' });
return { display: 'flex' };
});Creates a utility class generator. Returns a creator function that MUST be called.
const createPadding = utility('padding', ({ value }) => ({ padding: value }));
// Object syntax (explicit keys)
createPadding({ sm: ref(spacingSm), md: ref(spacingMd) });
// Array syntax (auto-generated keys)
createPadding([ref(spacingSm), '@spacing.md', '1rem']);
// With modifiers
createPadding({ sm: ref(spacingSm) }, [hover, focus]);Critical: Always invoke the creator function. Defining a utility without calling it produces no CSS.
Creates a reusable modifier that wraps utility declarations.
const hover = modifier('hover', ({ declarations }) => ({
'&:hover': declarations,
}));
// Multi-key modifier (for breakpoints)
const responsive = modifier(['sm', 'md', 'lg'], ({ key, declarations }) => ({
[`@media (min-width: ${breakpoints[key]}px)`]: declarations,
}));Creates a component variant system. At runtime, recipe({ color: 'primary' }) returns a class string.
recipe({
name: 'button',
base: { borderWidth: ref(borderWidthThin), borderStyle: 'solid' },
variants: {
color: {
primary: { background: ref(colorPrimary), color: ref(colorWhite) },
secondary: { background: ref(colorSecondary) },
},
size: {
sm: { padding: ref(spacingSm) },
md: { padding: ref(spacingMd) },
},
},
defaultVariants: { color: 'primary', size: 'md' },
compoundVariants: [
{
match: { color: 'primary', disabled: false },
css: { hover: { background: '@color.primary-dark' } },
},
],
});Creates a theme variant scoped to [data-theme="name"].
theme('dark', (ctx) => {
ctx.variable(colorBackground, '#18181b');
ctx.variable(colorText, '#ffffff');
ctx.selector('.card', { boxShadow: '0 4px 6px rgba(0,0,0,0.3)' });
});HTML: <div data-theme="dark">Dark themed content</div>
Interpolates token values into CSS strings for complex expressions.
padding: css`${ref(spacingSm)} ${ref(spacingMd)}`
width: css`calc(100% - ${ref(sidebarWidth)})`
background: css`linear-gradient(135deg, ${ref(colorPrimary)} 0%, ${ref(colorSecondary)} 100%)`Defines a CSS @keyframes animation. Returns an AtRule token with .rule for the animation name.
const fadeIn = keyframes('fade-in', {
'0%': { opacity: 0, transform: 'translateY(10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
selector('.animated', {
animation: `${fadeIn.rule} 0.3s ease-out`,
});Creates @media rules. Also usable inline in selectors via object keys.
media('(min-width: 768px)', ({ selector }) => {
selector('.container', { maxWidth: '750px' });
});Creates arbitrary CSS at-rules (@supports, @font-face, @layer, @container, @property).
atRule('supports', '(display: grid)', ({ selector }) => {
selector('.grid', { display: 'grid' });
});Combines multiple Styleframe instances. Variables override; arrays (utilities, modifiers, recipes) concatenate; same-name themes merge.
import { merge } from 'styleframe';
const s = merge(base, components, themes); // General → specificimport { styleframe } from 'styleframe';
import { useDesignTokensPreset, useModifiersPreset, useUtilitiesPreset } from '@styleframe/theme';
const s = styleframe();
const { colorPrimary, spacingMd } = useDesignTokensPreset(s); // Flat — all tokens directly
const modifiers = useModifiersPreset(s); // All pseudo-state/element/media modifiers
useUtilitiesPreset(s); // All utility classes
export default s;| Category | Composables |
|---|---|
| Colors | useColorDesignTokens, useColorLevelDesignTokens, useColorShadeDesignTokens, useColorTintDesignTokens |
| Scales | useScaleDesignTokens, useScalePowersDesignTokens |
| Spacing | useSpacingDesignTokens, useMultiplierDesignTokens |
| Typography | useFontFamilyDesignTokens, useFontSizeDesignTokens, useFontWeightDesignTokens, useFontStyleDesignTokens, useLineHeightDesignTokens, useLetterSpacingDesignTokens |
| Borders/Effects | useBorderWidthDesignTokens, useBorderRadiusDesignTokens, useBorderStyleDesignTokens, useBorderColorDesignTokens, useBoxShadowDesignTokens |
| Breakpoints | useBreakpointDesignTokens |
| Easing | useEasingDesignTokens |
| Duration | useDurationDesignTokens |
| Z-Index | useZIndexDesignTokens |
All composables take s (Styleframe instance) as first argument, accept optional custom values, and return typed token objects.
import { useColorDesignTokens, useSpacingDesignTokens, useMultiplierDesignTokens, useScaleDesignTokens, useScalePowersDesignTokens } from '@styleframe/theme';
const { colorPrimary } = useColorDesignTokens(s, { primary: '#006cff' } as const);
const { spacing } = useSpacingDesignTokens(s, { default: '1rem' } as const);
const { scale } = useScaleDesignTokens(s, { default: '@minor-third' });
const scalePowers = useScalePowersDesignTokens(s, scale);
const { spacingSm, spacingMd } = useMultiplierDesignTokens(s, spacing, {
sm: scalePowers[-1],
md: scalePowers[0],
});| Composable | Examples |
|---|---|
usePseudoStateModifiers |
hover, focus, focusWithin, focusVisible, active, visited |
usePseudoElementModifiers |
before, after, placeholder, selection, firstLetter |
useFormStateModifiers |
disabled, enabled, checked, required, valid, invalid |
useAriaStateModifiers |
ariaExpanded, ariaSelected, ariaDisabled, ariaPressed |
useMediaPreferenceModifiers |
dark, motionSafe, motionReduce, contrastMore, print |
useStructuralModifiers |
first, last, only, odd, even, empty |
useDirectionalModifiers |
rtl, ltr |
Typography, Backgrounds, Borders, Effects, Filters, Flexbox & Grid, Layout, Sizing, Interactivity, Transforms, Transitions, Accessibility, SVG, Tables.
import { useButtonRecipe, useBadgeRecipe } from '@styleframe/theme';
useButtonRecipe(s); // Colors: primary/secondary/success/info/warning/error
// Variants: solid/outline/soft/subtle/ghost/link
// Sizes: xs/sm/md/lg/xl
useBadgeRecipe(s); // Colors: same, Variants: solid/outline/soft/subtle, Sizes: sameimport { useShorthandUtilitiesPreset } from '@styleframe/theme';
useShorthandUtilitiesPreset(s); // Tailwind-style: m, p, w, text, etc.Each bundler has a dedicated adapter. Plugins are default exports.
// vite.config.ts
import styleframe from 'styleframe/plugin/vite';
export default defineConfig({ plugins: [styleframe()] });
// nuxt.config.ts
export default defineNuxtConfig({ modules: ['styleframe/plugin/nuxt'] });
// astro.config.mjs
import styleframe from 'styleframe/plugin/astro';
export default { integrations: [styleframe()] };styleframe({
entry: './styleframe.config.ts', // Config file path
include: ['**/*.styleframe.ts'], // Extension file globs
loadOrder: 'alphabetical', // or 'depth-first'
dts: { enabled: true, outDir: '.styleframe' },
scanner: {
content: ['./src/**/*.{html,jsx,tsx,vue}'], // Enable utility class auto-detection
},
});| Import | Context | Returns |
|---|---|---|
virtual:styleframe |
From *.styleframe.ts |
Global instance factory for extending |
virtual:styleframe |
From app code | Compiled recipe functions and selector constants |
virtual:styleframe.css |
Any | All compiled CSS styles |
// button.styleframe.ts
import { styleframe } from 'virtual:styleframe';
import { useButtonRecipe } from '@styleframe/theme';
const s = styleframe();
const { selector } = s;
export const button = useButtonRecipe(s);
selector('.button-grid', {
display: 'flex',
gap: '@spacing.md',
});
export default s;import { button } from 'virtual:styleframe';
import 'virtual:styleframe.css';
const classes = button({ color: 'primary', size: 'md' });
// => "button _border-width:thin _cursor:pointer _background:primary ..."| Type | Pattern | Example |
|---|---|---|
| Variables | use<Context>Variables() |
useColorVariables(s) |
| Selectors | use<Context>Selectors() |
useButtonSelectors(s) |
| Utilities | use<Context>Utilities() |
useSpacingUtilities(s) |
| Recipes | use<Context>Recipe() |
useButtonRecipe(s) |
All composable variables MUST use { default: true }:
export function useColorVariables(s: Styleframe) {
const { variable, ref } = s;
const colorPrimary = variable('color.primary', '#006cff', { default: true });
return { colorPrimary };
}styleframe init [cwd] # Scaffold project, install deps, configure build tool
styleframe build [entry] # Compile config to CSS/TS/DTS output
styleframe dtcg export # Export variables to spec-conformant DTCG JSON
styleframe dtcg import -i in.json # Generate Styleframe code from DTCG JSON- NEVER hardcode colors, spacing, or sizes — Use variables and
ref() - NEVER use
ref()without destructuring it from the instance - NEVER forget
{ default: true }in composable variables - NEVER define utilities without calling the creator function —
utility()returns a function - NEVER use arbitrary CSS values without
csstemplate literal for complex expressions - NEVER forget to export the Styleframe instance as default
- NEVER use appearance-based names — Use semantic names (
color.primarynotcolor.blue) - NEVER use named exports in index files — Use
export *for all re-exports - NEVER import from
virtual:styleframein non-*.styleframe.tsfiles expecting the global instance — only extension files get the extension face - NEVER import from
@styleframe/*sub-packages — Import from'styleframe'barrel package
Prefer LSP over Grep/Glob/Read for code navigation:
goToDefinition/goToImplementationto jump to sourcefindReferencesto see all usages across the codebaseworkspaceSymbolto find where something is defineddocumentSymbolto list all symbols in a filehoverfor type info without reading the fileincomingCalls/outgoingCallsfor call hierarchy
Before renaming or changing a function signature, use findReferences to find all call sites first. Use Grep/Glob only for text/pattern searches where LSP does not help. After writing or editing code, check LSP diagnostics before moving on.
GitHub Actions linting: Editing files under .github/workflows/ runs actionlint via lint-staged on pre-commit. Install it once locally so the hook can run:
- macOS:
brew install actionlint - Other platforms: see https://github.com/rhysd/actionlint/blob/main/docs/install.md
Run pnpm lint:actions to lint all workflows manually.
Each package has its own AGENTS.md with detailed API reference, types, and conventions:
| Package | AGENTS.md Path |
|---|---|
| Core engine | engine/core/AGENTS.md |
| Barrel package | engine/styleframe/AGENTS.md |
| Loader | engine/loader/AGENTS.md |
| Transpiler | engine/transpiler/AGENTS.md |
| Runtime | engine/runtime/AGENTS.md |
| Scanner | engine/scanner/AGENTS.md |
| Theme | theme/AGENTS.md |
| Build plugin | tooling/plugin/AGENTS.md |
| CLI | tooling/cli/AGENTS.md |
| Figma | tooling/figma/AGENTS.md |
| Integration tests | testing/integration/AGENTS.md |
| Storybook | apps/storybook/AGENTS.md |
| Documentation site | apps/docs/content/docs/AGENTS.md |
| Customer app | apps/app/AGENTS.md |
| Shared Nuxt layer | apps/shared/AGENTS.md |
| Build config | config/AGENTS.md |
.claude/styleframe-api.md— Complete API reference.claude/styleframe-patterns.md— Common patterns and examples.claude/styleframe-recipes.md— Recipe system guide.claude/styleframe-tokens.md— Design token composables