Tailwindest

css-transformer

Migrate Tailwind class strings to Tailwindest object styles.

tailwindest css transformer banner

tailwindest-css-transform is a migration tool. It rewrites supported Tailwind class-string patterns into Tailwindest object-style calls, while preserving source code when exact conversion is not proven.

Use it when you are moving an existing Tailwind codebase from className, cn, clsx, classNames, or cva strings into Tailwindest styles.

Install

Run the transformer directly:

npx tailwindest-css-transform

Or install it for repeated local use:

pnpm add -D tailwindest-css-transform

The CLI opens an interactive prompt for target path, Tailwind CSS entry, Tailwindest import name, import path, enabled walkers, and dry-run mode.

What It Converts

Input:

import { cn } from "@/lib/utils"

export function Button() {
    return (
        <button
            className={cn(
                "flex items-center rounded-md bg-red-50 px-4 py-2",
                "dark:hover:bg-red-950"
            )}
        />
    )
}

Runtime-mode output:

import { tw } from "~/tw"

const buttonButton = tw.style({
    display: "flex",
    alignItems: "items-center",
    borderRadius: "rounded-md",
    backgroundColor: "bg-red-50",
    paddingLeft: "px-4",
    paddingTop: "py-2",
    dark: {
        hover: {
            backgroundColor: "dark:hover:bg-red-950",
        },
    },
})

export function Button() {
    return <button className={buttonButton.class()} />
}

Runtime-mode output keeps prefixed nested leaves:

tw.style({
    dark: {
        hover: {
            backgroundColor: "dark:hover:bg-red-950",
        },
    },
})

Output Modes

The transformer is runtime-first:

ModeTargetLeaf value
runtimeCreateTailwindestoriginal token, such as dark:hover:bg-red-950
autosafe defaultruntime output
# Safe default for mixed or unknown projects
npx tailwindest-css-transform --mode auto

# Runtime Tailwindest migration
npx tailwindest-css-transform --mode runtime

auto keeps runtime output for unknown or mixed projects.

Runtime Tailwindest Setup

Define your Tailwindest type with CreateTailwindest:

import {
    createTools,
    type CreateTailwindest,
    type CreateTailwindLiteral,
} from "tailwindest"
import type { Tailwind, TailwindNestGroups } from "./tailwind"

export type Tailwindest = CreateTailwindest<{
    tailwind: Tailwind
    tailwindNestGroups: TailwindNestGroups
    useArbitrary: true
}>

export type TailwindLiteral = CreateTailwindLiteral<Tailwind>

export const tw = createTools<{
    tailwindest: Tailwindest
    tailwindLiteral: TailwindLiteral
}>()

Transform with runtime output:

npx tailwindest-css-transform --mode runtime

Nested variant leaves preserve the original prefixed class:

tw.style({
    dark: {
        hover: {
            backgroundColor: "dark:hover:bg-red-950",
        },
    },
})

Supported Patterns

The transformer currently supports static class strings in:

  • className="..."
  • className={"..."}
  • cn("...")
  • clsx("...")
  • classNames("...")
  • cva("...")
  • cva(..., { variants: { ... } }) static string options

Dynamic arguments are preserved when the surrounding call can still be represented safely.

cn("flex px-4", isActive && "bg-red-500", props.className)

The static part can become a Tailwindest style constant, while dynamic arguments remain in the generated .class(...) call.

Conservative Fallback

The transformer does not rewrite code it cannot prove safe.

Common fallback cases:

  • template literals with substitutions
  • computed class strings
  • unknown or ambiguous Tailwind utilities
  • runtime-generated cva variant maps
  • unsupported compoundVariants conversion
  • helper imports still used elsewhere

Run dry-run first and inspect diagnostics before writing files.

CLI Options

npx tailwindest-css-transform --mode auto --dry-run
OptionAliasDefaultDescription
--css <path>-ctailwind.cssTailwind CSS entry used to initialize the resolver.
--identifier <name>-itwTailwindest import identifier.
--module <path>-m~/twTailwindest module import path.
--dry-run-dfalsePreview without writing transformed files.
--mode <mode>noneautoOutput mode: auto or runtime.

Programmatic Usage

import { transform } from "tailwindest-css-transform"

const result = await transform(source, {
    resolver,
    outputMode: "runtime",
    projectRoot: process.cwd(),
    sourcePath: "/repo/src/Button.tsx",
    tailwindestIdentifier: "tw",
    tailwindestModulePath: "~/tw",
    walkers: ["cva", "cn", "classname"],
    config: {
        objectThreshold: 2,
    },
})

console.log(result.code)
console.log(result.diagnostics)

You normally do not need the programmatic API unless you are building custom migration tooling.

Safety Model

The transformer uses Collect -> Reverse Execute:

  1. Parse source with ts-morph.
  2. Collect supported transform targets.
  3. Analyze static class strings.
  4. Apply replacements from the end of the file to the beginning.
  5. Apply import edits once.
  6. Return transformed code and diagnostics.

This avoids stale AST ranges and keeps unrelated source code untouched.

On this page