From TailwindCSS
You already think in utilities. That instinct carries over: Styleframe also generates atomic classes you compose in markup, and both tools compile to static CSS with zero runtime cost. What changes is where the vocabulary comes from. In Tailwind the utility set is fixed by the framework; in Styleframe you define design tokens in TypeScript and the utilities are generated from them. This guide maps one vocabulary onto the other so you can move a codebase over piece by piece.
The one difference that explains the rest
Tailwind reads bg-blue-500 from your markup and emits the matching CSS. Styleframe works the other way around: you register a design token (say colors.primary), and Styleframe generates a _background:primary utility from it. The class name encodes the CSS property and the token, not a preset scale value.
| Tailwind CSS | Styleframe | |
|---|---|---|
| Class shape | bg-blue-500 (abbreviation + scale) | _background:primary (_property:value) |
| Source of the scale | Framework defaults + @theme | Your design tokens, in TypeScript |
| Arbitrary value | p-[17px] | _padding:[17px] |
| State variant | hover:bg-blue-600 | _hover:background:primary-600 |
| Type safety | Class strings (IDE plugin) | Compile-time checked tokens |
Once you internalize _property:value, most of the migration is mechanical. The tables below give you the substitutions.
Step 1: Install Styleframe
Run the initializer in your project root, then import the generated stylesheet once at your app entry.
pnpx styleframe init
pnpm install
npx styleframe init
npm install
bunx styleframe init
bun install
import 'virtual:styleframe.css';
Then add the five presets to styleframe.config.ts. They are the closest equivalent to Tailwind's "batteries included" defaults: a full token system, a CSS reset, semantic element styling, roughly 200 utility classes, and state modifiers.
import {
useDesignTokensPreset,
useGlobalPreset,
useModifiersPreset,
useSanitizePreset,
useUtilitiesPreset,
} from '@styleframe/theme';
import { styleframe } from 'styleframe';
const s = styleframe();
useDesignTokensPreset(s);
useSanitizePreset(s);
useGlobalPreset(s);
useUtilitiesPreset(s);
useModifiersPreset(s);
export default s;
useSanitizePreset plays the role of Tailwind's Preflight — box-sizing, margin resets, and form-element normalization via sanitize.css. useUtilitiesPreset is what generates the utility classes the tables below reference.Step 2: Map the utilities
The default presets ship Tailwind-flavored value keys on purpose — _justify-content:between, _flex-direction:col, _display:hidden — so much of the translation is muscle memory. Where a value falls between token steps, drop to the bracket syntax (_padding:[17px]).
useUtilitiesPreset for useShorthandUtilitiesPreset in Step 1 and the same utilities generate as _p:md, _m:sm, _rounded:lg instead of _padding:md, _margin:sm, _border-radius:lg — only the names change; grammar and tokens stay put. Read the tables below as a mechanical name swap.Spacing
Tailwind's numeric spacing scale maps onto Styleframe's named spacing tokens. The named scale is coarser by default; use the bracket syntax or a multiplier (_padding:@1.5 → calc(var(--spacing) * 1.5)) for in-between values.
| Tailwind | Value | Styleframe |
|---|---|---|
p-1 | 0.25rem | _padding:2xs |
p-2 | 0.5rem | _padding:xs |
p-3 | 0.75rem | _padding:sm |
p-4 | 1rem | _padding:md |
p-6 | 1.5rem | _padding:lg |
p-8 | 2rem | _padding:xl |
p-12 | 3rem | _padding:2xl |
p-16 | 4rem | _padding:3xl |
px-4 | inline | _padding-inline:md |
py-4 | block | _padding-block:md |
pt-4 / pr-4 / pb-4 / pl-4 | one side | _padding-top:md / _padding-right:md / _padding-bottom:md / _padding-left:md |
m-4 | 1rem | _margin:md |
mx-auto | center | _margin-inline:auto |
gap-2 | 0.5rem | _gap:xs |
p-[17px] | arbitrary | _padding:[17px] |
The same key scale (2xs through 3xl) applies to margin, gap, gap-x, and gap-y.
Layout and flexbox
| Tailwind | Styleframe |
|---|---|
flex | _display:flex |
inline-flex | _display:inline-flex |
grid | _display:grid |
block | _display:block |
hidden | _display:hidden |
flex-row | _flex-direction:row |
flex-col | _flex-direction:col |
flex-wrap | _flex-wrap:wrap |
flex-1 | _flex:1 |
items-center | _align-items:center |
items-start | _align-items:start |
justify-between | _justify-content:between |
justify-end | _justify-content:end |
justify-center | _justify-content:center |
Sizing
| Tailwind | Styleframe |
|---|---|
w-full | _width:full |
w-screen | _width:screen |
w-1/2 | _width:1/2 |
h-full | _height:full |
max-w-[640px] | _max-width:[640px] |
min-w-0 | _min-width:[0] |
Typography
| Tailwind | Styleframe |
|---|---|
text-sm | _font-size:sm |
text-base | _font-size:md |
text-lg | _font-size:lg |
text-2xl | _font-size:2xl |
font-medium | _font-weight:medium |
font-semibold | _font-weight:semibold |
font-bold | _font-weight:bold |
leading-tight | _line-height:tight |
leading-relaxed | _line-height:relaxed |
text-left | _text-align:left |
text-center | _text-align:center |
Colors, backgrounds, and borders
This is the biggest conceptual shift. Tailwind uses a fixed palette keyed by hue and shade (blue-500, gray-100). Styleframe uses semantic color tokens — primary, secondary, success, text, background — and generates an eleven-step lightness ramp (-50 through -950) plus shades and tints for each one. You map Tailwind's blue-* to your own primary-*, not to a fixed blue.
| Tailwind | Styleframe |
|---|---|
bg-blue-500 | _background:primary (or _background:primary-500) |
bg-blue-600 | _background:primary-600 |
text-white | _color:white |
text-gray-600 | _color:text-weak |
bg-gray-100 | _background:gray-100 |
bg-[#1da1f2] | _background:[#1da1f2] |
rounded | _border-radius:md |
rounded-lg | _border-radius:lg |
rounded-full | _border-radius:full |
primary-50…primary-950) are computed in OKLCH, so the ramp stays perceptually even. Tailwind's numeric shades and Styleframe's levels are close but not identical values — map by role, not by exact hex.Step 3: Map the modifiers
Tailwind prefixes a variant onto the class (hover:bg-blue-600). Styleframe uses the same idea with its own format: _<modifier>:<property>:<value>.
| Tailwind | Styleframe |
|---|---|
hover:bg-blue-600 | _hover:background:primary-600 |
focus-visible:outline-2 | _focus-visible:outline-width:[2px] |
active:bg-blue-700 | _active:background:primary-700 |
disabled:opacity-50 | _disabled:opacity:[0.5] |
dark:bg-gray-900 | _dark:background:gray-900 |
md:flex | _md:display:flex |
lg:px-8 | _lg:padding-inline:xl |
useModifiersPreset registers pseudo-classes (_hover:, _focus:, _focus-visible:, _active:), form states (_disabled:, _checked:, _invalid:), structural selectors (_first:, _odd:), pseudo-elements (_before:, _after:, _placeholder:), media preferences (_dark:, _motion-safe:, _print:), and responsive breakpoints (_sm:, _md:, _lg:, _xl:, _2xl:).
The prefix names line up with Tailwind's — _sm: through _2xl:, mobile-first — but the default widths behind them do not. Styleframe uses the 576 / 768 / 992 / 1200 / 1440 scale, so only _md: (768px) matches Tailwind's default. useModifiersPreset ships these breakpoint modifiers out of the box, so _md:padding:xl resolves to a real @media (min-width: 768px) block with no extra setup — the same _property:value shape, just gated behind a min-width query:
| Prefix | Media query |
|---|---|
_sm: | @media (min-width: 576px) |
_md: | @media (min-width: 768px) |
_lg: | @media (min-width: 992px) |
_xl: | @media (min-width: 1200px) |
_2xl: | @media (min-width: 1440px) |
<div class="_display:block _md:display:flex _lg:gap:xl">…</div>
useModifiersPreset(s, { responsive: false }) and register your own with modifier(), closing each one over the query you want. See Utility Modifiers for the full API.Step 4: Translate the config
Tailwind v4 configures theme values in CSS with @theme. Styleframe configures them in TypeScript by passing options to useDesignTokensPreset. Custom values merge with the defaults, so you only list what you add or change.
@import "tailwindcss";
@theme {
--color-primary: #0066ff;
--color-secondary: #7c3aed;
--font-sans: "Inter", sans-serif;
--radius-lg: 0.5rem;
}
import { useDesignTokensPreset } from '@styleframe/theme';
import { styleframe } from 'styleframe';
const s = styleframe();
useDesignTokensPreset(s, {
colors: {
primary: '#0066ff',
secondary: '#7c3aed',
},
fontFamily: {
base: '"Inter", sans-serif',
},
});
export default s;
One config option replaces several Tailwind concerns at once. Because Styleframe generates the full primary-50…primary-950 ramp from a single primary value, you rarely hand-list shades the way a Tailwind palette does.
Dark mode
In Tailwind you toggle the dark variant and repeat colors at every call site (dark:bg-gray-900). In Styleframe, dark mode is a token override: declare the dark values once and every recipe and utility that references those tokens updates automatically.
useDesignTokensPreset(s, {
colors: {
primary: '#0066ff',
background: '#ffffff',
text: '#0f172a',
},
themes: {
dark: {
colors: {
primary: '#60a5fa',
background: '#0f172a',
text: '#f1f5f9',
},
},
},
});
The preset emits a [data-theme="dark"] block. Toggle it on the root element:
document.documentElement.dataset.theme = 'dark';
The Theme Switcher guide covers system-preference detection and persistence.
Step 5: Replace variant helpers with recipes
Tailwind has no built-in component variant system, so teams reach for CVA or Tailwind Variants. Styleframe ships that pattern in the core API as recipes — and because they compile to typed functions, invalid variants are caught at build time instead of failing silently in a class string.
import { cva } from 'class-variance-authority';
export const button = cva('rounded font-medium', {
variants: {
color: {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
},
size: {
sm: 'text-sm px-3 py-1',
lg: 'text-lg px-6 py-3',
},
},
defaultVariants: { color: 'primary', size: 'sm' },
});
import { styleframe } from 'styleframe';
const s = styleframe();
const { recipe } = s;
recipe({
name: 'button',
base: {
borderRadius: '@border-radius.md',
fontWeight: '@font-weight.medium',
},
variants: {
color: {
primary: {
background: '@color.primary',
color: '@color.white',
'hover': { background: '@color.primary-600' },
},
secondary: {
background: '@color.gray-200',
color: '@color.text',
'hover': { background: '@color.gray-300' },
},
},
size: {
sm: { fontSize: '@font-size.sm', paddingInline: '@spacing.sm', paddingBlock: '@spacing.2xs' },
lg: { fontSize: '@font-size.lg', paddingInline: '@spacing.lg', paddingBlock: '@spacing.sm' },
},
},
defaultVariants: { color: 'primary', size: 'sm' },
});
export default s;
This block layers on the Step 1 config — it references utilities and tokens the presets register, so run it on that same instance rather than a bare styleframe().
Register the recipe in a co-located *.styleframe.ts file and import the generated function to compute classes:
import { useButtonRecipe } from '@styleframe/theme';
import { styleframe } from 'virtual:styleframe';
const s = styleframe();
export const button = useButtonRecipe(s);
export default s;
useButtonRecipe ships a full color × variant × size matrix out of the box. Pass filter to prune combinations you don't use: useButtonRecipe(s, { filter: { variant: ['solid', 'outline'] } }). Browse the component catalog for the rest.A full component, before and after
<button class="px-4 py-2 bg-blue-500 text-white font-semibold rounded-md hover:bg-blue-600">
Save changes
</button>
<button class="_padding-inline:md _padding-block:xs _background:primary _color:white _font-weight:semibold _border-radius:md _hover:background:primary-600">
Save changes
</button>
The shape is the same; the class names now point at your tokens instead of a fixed palette. For anything beyond one-off styling, reach for a recipe so the variant contract lives in one typed place.
What to watch for
| If you relied on… | In Styleframe… |
|---|---|
Arbitrary values everywhere (p-[13px]) | Supported (_padding:[13px]), but prefer tokens for consistency |
Responsive prefixes (md:flex) | Built-in _sm:–_2xl: prefixes — same names, but only _md: shares Tailwind's default width (Step 3) |
A fixed color palette (blue-500) | Semantic tokens with generated -50…-950 levels |
@apply to build components | Author a recipe — a typed variant function |
| CVA / Tailwind Variants | Built-in recipe() and the use*Recipe composables |
| The Tailwind IDE plugin | TypeScript autocomplete and compile-time checks |
Next steps
- Get the whole system fast: Create a Design System in Under 15 Minutes.
- Go deep on tokens: the Design Tokens Preset API documents every option and default.
- Master utilities and modifiers: Utilities and Utility Modifiers.
- Author recipes: the Recipes API covers variants, compound variants, and defaults.