Skip to content

Migrate from Tailwind

The csszyx migrate CLI command converts className= (JSX/TSX) or class= (HTML) attributes to sz= props automatically. It handles static strings, clsx calls, ternary expressions, and template literals.

Terminal window
npx csszyx migrate src/ # migrate all JSX/TSX/HTML under src/
npx csszyx migrate --dry-run # preview changes without writing files
npx csszyx migrate --ignore "**/*.test.tsx,**/fixtures/**"
npx csszyx migrate --pattern "src/components/**/*.tsx"

Migration logs are written to .csszyx/logs/. Add .csszyx/ to .gitignore.

Before migrating, run an audit to see what csszyx cannot automatically convert:

Terminal window
npx csszyx migrate --audit

This scans your codebase and writes .csszyx-todo.json without touching any source files. Each entry starts as "sz:todo":

{
"btn": "sz:todo",
"custom-card": "sz:todo",
"animate-spin-slow": "sz:todo"
}

Edit .csszyx-todo.json to tell csszyx what to do with each class:

ValueMeaning
"sz:todo"Not yet decided — skip, surface in reports
"sz:keep"Keep in className, acknowledged as intentional
"sz:remove"Drop from output entirely
{ p: 4, bg: 'blue-500' }Direct sz object — merged into sz prop
"p-4 bg-blue-500"Tailwind string — auto-converted to sz
null / falseSame as "sz:todo" (backwards compat)

--resolve-todos — Apply the Resolution Map

Section titled “--resolve-todos — Apply the Resolution Map”
Terminal window
npx csszyx migrate --resolve-todos .csszyx-todo.json

Reads .csszyx-todo.json and applies it during migration. Classes mapped to sz:keep stay in className; sz:remove entries are dropped; sz objects and Tailwind strings are converted.

--resolve-todos is read-only — it never writes to the todo file. Still-unresolved sz:todo entries appear in the console and log only.

--inject-todos — Mark Unresolved Classes in Code

Section titled “--inject-todos — Mark Unresolved Classes in Code”
Terminal window
npx csszyx migrate --inject-todos

Inserts {/* @sz-todo: classname1, classname2 */} comments above JSX elements that still have unrecognized classes — a visual marker so you can grep or skim the diff to find what needs attention.

When --resolve-todos is active, --inject-todos is automatically enabled for any still-unresolved classes.

Terminal window
# 1. Dry run — preview what will change
npx csszyx migrate --dry-run
# 2. Audit — find unrecognized classes
npx csszyx migrate --audit
# → writes .csszyx-todo.json
# 3. Edit .csszyx-todo.json
# → set "sz:keep", "sz:remove", or direct sz objects for each entry
# 4. Apply with resolution map
npx csszyx migrate --resolve-todos .csszyx-todo.json
# 5. Re-audit if anything remains unresolved
npx csszyx migrate --audit

For plain HTML files (no JSX build step), csszyx converts class="..." to sz="..." attributes. A runtime script is needed at page load to process them — the migration command can inject it for you.

By default (csszyx migrate public/), the command:

  • Converts class="..."sz="..."
  • Injects FOUC prevention CSS into <head>
  • Does not inject a runtime script ❌
Terminal window
# CDN (default URL: https://cdn.csszyx.com/runtime.js)
npx csszyx migrate public/ --inject-runtime cdn
# Local file (default path: csszyx-runtime.js, relative to each HTML file)
npx csszyx migrate public/ --inject-runtime local
# Custom CDN URL
npx csszyx migrate public/ --inject-runtime cdn --cdn-url https://my-cdn.com/csszyx.js
# Custom local path
npx csszyx migrate public/ --inject-runtime local --local-path ./vendor/csszyx-runtime.js

The runtime script tag is injected before </body>.

Enabled by default. Injects this block before </head>:

<style>
/* csszyx: hide [sz] elements until runtime processes them */
[sz] { visibility: hidden; }
body.sz-ready [sz] { visibility: visible; }
</style>

Pass --no-fouc to skip this if you are managing the loading transition yourself.

Controls the format of the generated sz attribute value.

Without --braces (default — bare object contents):

<div sz="p: 4, bg: 'blue-500'"></div>

With --braces (full object syntax):

<div sz="{ p: 4, bg: 'blue-500' }"></div>

Use --braces if your HTML is processed by a template engine that expects full object literal syntax.

The todo map types are exported for use in custom tooling:

import type { CsszyxTodoEntry, CsszyxTodoMap } from '@csszyx/cli';
// CsszyxTodoEntry = Record<string, unknown> | string | null | false
// CsszyxTodoMap = Record<string, CsszyxTodoEntry>