Styleframe vs. Vanilla Extract
Overview
Both Styleframe and Vanilla Extract generate static CSS at build time while letting you write styles in TypeScript. They share the same big benefits (zero runtime on the client, type safety, SSR-friendly output), but they differ in how much structure they provide out of the box.
- Styleframe: design-system-first APIs (tokens, themes, utilities, recipes) with guided conventions.
- Vanilla Extract: a minimal, low-level core that you compose with additional packages/patterns as your system grows.
Who this comparison is for
This document is written for teams who are:
- Building or formalizing a design system (not just styling a single app).
- Managing tokens + theming (light/dark, brand themes, product variants).
- Coordinating patterns across multiple contributors and multiple component libraries/apps.
If your main goal is "write type-safe styles in TS and extract CSS," both are strong options. The choice usually comes down to how much standard structure you want for tokens, themes, variants, and utilities.
Scope of comparison
Where relevant, the comparison below considers:
- Styleframe, which provides an integrated "design system toolkit" (variables/tokens, themes, utilities, recipes) with consistent conventions.
- versus Vanilla Extract, plus the common companion pieces teams adopt for design systems (theme contracts/themes, recipes, sprinkles/atomics, internal conventions),
Decision criteria
We’ll compare across the things that tend to matter most in large-scale systems:
- System structure & governance (how easy it is to standardize patterns)
- Tokens & theming (how tokens are defined, referenced, and evolved)
- Variants & recipes (component variant ergonomics and type safety)
- Utilities / atomics (layout primitives, spacing, color utilities)
- Developer experience & onboarding (how quickly a team converges on shared practices)
- SSR & performance (how CSS is emitted and consumed)
- Scaling over time (what you typically add as the system grows)
At a glance
| Area | Styleframe | Vanilla Extract |
|---|---|---|
| Core philosophy | Design Systems toolkit (tokens + patterns) | Minimal primitives (compose your own system) |
| Tokens | Built-in variables + refs | Theme contracts/vars (team conventions on top) |
| Variants | Built-in recipe() | Commonly via @vanilla-extract/recipes |
| Utilities / atomics | Built-in utility() patterns | Often via Sprinkles or custom utilities |
| DX onboarding | Guided setup + conventions | Install + bundler plugin |
| Output | Static CSS (global or per-file) | Static CSS extracted from .css.ts |
| Best for | Large design systems, multi-team consistency | Teams wanting a thin, flexible base |
Detailed comparison
1. System structure & governance
What teams typically need
As systems grow, the hard part often becomes consistency:
- Shared token naming and usage patterns
- A clear place to put "system primitives" (themes, utilities, recipes)
- A way to avoid drift across packages, repos, and teams
Styleframe
- Convention-driven: common design system building blocks map to dedicated APIs (variables, composables, recipes).
- "Batteries included" workflow: tokens, themes, variants, and utilities share one mental model.
- Works well when you want consistent patterns across many contributors.
Vanilla Extract
- The core is intentionally small: typed style objects returning class names.
- You decide the architecture: tokens, variants, and utilities are assembled from core + ecosystem packages and/or internal patterns.
- Great for teams that want maximum control and don’t mind establishing internal conventions.
2. Tokens and variables
What teams typically need
- Tokens defined once and referenced everywhere
- Safe refactoring (renames, deprecations, migrations)
- A predictable naming scheme and scoping model
Styleframe
Styleframe treats design tokens as first-class and promotes dot-notation naming that compiles to CSS custom properties.
Styleframe also emphasizes token references (variable() + ref()) and composables that can reduce accidental duplication (e.g., via default: true patterns when composing).
Vanilla Extract
Vanilla Extract commonly models tokens using theme contracts and vars, which you then reference in style() calls.
A typical shape is:
- define a contract (the "shape" of tokens),
- create one or more themes that fill that contract,
- reference the resulting
varsin component styles.
This can be very explicit and flexible, especially when you want full control over how tokens are organized across packages.
Both approaches can produce clean CSS variables. The practical difference is usually whether token definitions, theming, and reuse patterns are integrated into one toolkit (Styleframe) or composed from primitives + conventions (Vanilla Extract).
3. Theming
What teams typically need
- Light/dark themes
- Brand/product themes
- Scoping (root, subtree, per-app) and overrides
Styleframe
Styleframe provides a dedicated theme() API that integrates with its variable system. Themes are added using data-theme or custom selectors, which simplifies switching and supports nested themes (e.g., a dark sidebar inside a light app). You can override both variables and selectors within a theme definition.
Vanilla Extract
Vanilla Extract commonly uses theme contracts + theme classes, which can be a strong fit if you prefer theming to be an explicit, modular layer you can reorganize as your system evolves. Teams apply themes by toggling CSS classes.
4. Variants and component recipes
What teams typically need
- A standard way to model component variants (size, tone, intent)
- Type-safe selection of variants
- A consistent pattern for compound variants and defaults
Styleframe
- Styleframe ships a recipe concept as part of the core "design system toolkit".
- Recipes model component variants (e.g., size, tone) with a type-safe selector that outputs class names.
Vanilla Extract
- Vanilla Extract typically pulls variants in via
@vanilla-extract/recipes. - This keeps the core minimal and lets teams opt into variants where needed, but it also means you’ll define (or adopt) conventions for how recipes are structured and shared across packages.
5. Utilities / atomics
What teams typically need
- Layout primitives and spacing utilities
- A consistent "utility vocabulary" across teams
- A way to avoid one-off ad-hoc utility creation
Styleframe
- Built-in
utility()patterns can make it straightforward to define and share a consistent utility layer. - Because utilities live alongside tokens/themes/recipes, teams often end up with a single place to learn "how we build styles here."
Vanilla Extract
- Many teams reach for Sprinkles (or build custom utilities).
- This can be a good fit if you want utilities to be a separate, carefully scoped layer - or if you want to avoid utilities entirely and rely on component styles.
6. Developer experience and onboarding
Styleframe
- CLI-driven setup + conventions can reduce "blank page" decisions.
- Dedicated primitives (variables/composables/recipes) can make systems more discoverable, especially for new contributors.
- New team members can ramp up faster when tokens, themes, and utilities follow a consistent pattern.
Vanilla Extract
- Very approachable if you like working close to CSS concepts.
- Install + bundler plugin; conventions are up to you, which works well when your team already has strong style architecture preferences.
7. Type safety and reliability
Both approaches are strongly typed because you author styles in TypeScript and extract to static CSS.
Styleframe
- Emphasizes token references (
variable()+ref()) and composables that can avoid accidental duplication via patterns likedefault: true.
Vanilla Extract
- Emphasizes theme contracts and typed
varsusage, with additional type-safe helpers via ecosystem packages.
8. SSR and performance
Both are zero-runtime on the client:
- CSS is extracted at build time.
- SSR/SSG frameworks can serve HTML with classes already applied and CSS already linked/loaded.
The only "runtime" work in either approach is optional variant selection helpers (e.g., recipe functions) which generally resolve to class names.
9. Scaling over time
A practical way to think about the difference is how each approach tends to evolve:
- Day 1: both feel straightforward - type-safe TS authoring with extracted CSS.
- Month 3: teams usually standardize tokens/themes/variants/utilities and document conventions.
- Year 1: consistency, enforcement, and discoverability matter as much as raw styling capability.
Styleframe’s value often shows up when you want tokens/themes/variants/utilities to remain cohesive as the system grows, while Vanilla Extract’s value often shows up when you want to keep the base minimal and assemble only what you need.
Side-by-side example (same outcome, different assembly)
Below is a conceptual "same goal" example: define tokens, apply a theme, and create a button with variants.
The goal here isn’t to show the only way to do it in either ecosystem - just a representative "design system" shape.
Styleframe
import { styleframe } from "styleframe";
const s = styleframe();
const { variable, ref, selector /* recipe, theme, utility, ... */ } = s;
const colorPrimary = variable("color.primary", "#006cff");
const colorWhite = variable("color.white", "#ffffff");
const borderRadiusSm = variable("border-radius.sm", "6px");
const spacingMd = variable("spacing.md", "1rem");
/**
* Minimal example: a button style referencing tokens.
* (Variants would typically be modeled via recipe() in Styleframe.)
*/
selector(".button", {
backgroundColor: ref(colorPrimary),
color: ref(colorWhite),
borderRadius: ref(borderRadiusSm),
padding: ref(spacingMd),
});
export default s;
<button class="button">Click me</button>
Vanilla Extract
import { createThemeContract, createTheme } from "@vanilla-extract/css";
export const vars = createThemeContract({
color: {
primary: null,
white: null,
},
radius: {
sm: null,
},
spacing: {
md: null,
},
});
export const lightTheme = createTheme(vars, {
color: {
primary: "#006cff",
white: "#ffffff",
},
radius: {
sm: "6px",
},
spacing: {
md: "1rem",
},
});
import { style } from "@vanilla-extract/css";
import { vars } from "./vanilla-extract.css";
export const button = style({
background: vars.color.primary,
color: vars.color.white,
borderRadius: vars.radius.sm,
padding: vars.spacing.md,
});
<button class={button}>Click me</button>
Choosing between them
A practical heuristic: if you find yourself needing tokens + themes + recipes + atomic utilities early, an integrated toolkit can reduce the number of separate building blocks you need to adopt and document.
Choose Styleframe if…
- You’re building a large-scale design system with multiple token domains (color, spacing, typography, radii, etc.).
- You want one consistent set of primitives for tokens, themes, utilities, and variants.
- You expect many contributors and want conventions to be discoverable and enforced.
Choose Vanilla Extract if…
- You want a very minimal core and prefer assembling only what you need.
- Your team already has established conventions for tokens/variants/utilities and doesn’t want additional abstraction.
- You’re optimizing for incremental adoption inside an existing codebase with established build tooling.