Styleframe Logo
Migrations

From TailwindCSS

Port a Tailwind CSS project to Styleframe by mapping utilities, config, and variant helpers side by side.

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.

This is the how. For the why — the trade-offs between a fixed utility framework and a design-system toolkit — read the Styleframe vs Tailwind CSS comparison first.

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 CSSStyleframe
Class shapebg-blue-500 (abbreviation + scale)_background:primary (_property:value)
Source of the scaleFramework defaults + @themeYour design tokens, in TypeScript
Arbitrary valuep-[17px]_padding:[17px]
State varianthover:bg-blue-600_hover:background:primary-600
Type safetyClass 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
main.ts
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.

styleframe.config.ts
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]).

Want Tailwind's short names? Swap 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.5calc(var(--spacing) * 1.5)) for in-between values.

TailwindValueStyleframe
p-10.25rem_padding:2xs
p-20.5rem_padding:xs
p-30.75rem_padding:sm
p-41rem_padding:md
p-61.5rem_padding:lg
p-82rem_padding:xl
p-123rem_padding:2xl
p-164rem_padding:3xl
px-4inline_padding-inline:md
py-4block_padding-block:md
pt-4 / pr-4 / pb-4 / pl-4one side_padding-top:md / _padding-right:md / _padding-bottom:md / _padding-left:md
m-41rem_margin:md
mx-autocenter_margin-inline:auto
gap-20.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

TailwindStyleframe
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

TailwindStyleframe
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

TailwindStyleframe
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.

TailwindStyleframe
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
The generated levels (primary-50primary-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>.

TailwindStyleframe
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:

PrefixMedia 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>
Need a different scale or your own breakpoint names? The modifiers are just functions. Turn off the defaults with 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;
}

One config option replaces several Tailwind concerns at once. Because Styleframe generates the full primary-50primary-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.

styleframe.config.ts
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' },
});

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:

src/components/button.styleframe.ts
import { useButtonRecipe } from '@styleframe/theme';
import { styleframe } from 'virtual:styleframe';

const s = styleframe();

export const button = useButtonRecipe(s);

export default s;
You don't have to author the button recipe by hand. 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>

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 componentsAuthor a recipe — a typed variant function
CVA / Tailwind VariantsBuilt-in recipe() and the use*Recipe composables
The Tailwind IDE pluginTypeScript autocomplete and compile-time checks

Next steps