npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tailwindest-css-transform

v1.0.10

Published

Tailwindest CSS Transformer

Readme

tailwindest-css-transform

Automate your migration from standard Tailwind CSS to type-safe Tailwindest objects.

Features

  • Zero-Config Migration: Automatically detects your tailwindest setup (namespace, paths) from your project.
  • Smart Auto-Import: Inserts necessary import statements into transformed files automatically.
  • Source-Safe: Uses AST (Abstract Syntax Tree) traversal to ensure code logic remains untouched.
  • Type-Safe: Generates objects that are 100% compatible with tailwindest types.
  • Typeset-Aware Resolution: Emits only actual generated Tailwindest record keys. Utility classification is checked against the generated Tailwind interface, with Tailwind compiler CSS used only to disambiguate semantics.
  • Lossless Static Token Preservation: Keeps Tailwind selector anchors, arbitrary declarations, plugin utilities, and unresolved static tokens in the generated class stream.
  • Selector-Chain Safety: Preserves complex universal descendant selector chains as raw Tailwind tokens instead of forcing unstable object DSL output.
  • Registry-Hardened: Validated against shadcn registry fixtures with merge stability and output token-preservation specs.

Installation

# Run directly with npx
npx tailwindest-css-transform <target> [options]

# Or install globally
npm install -g tailwindest-css-transform

Usage

# Transform a single file
npx tailwindest-css-transform src/components/Button.tsx

# Transform an entire directory recursively
npx tailwindest-css-transform src/pages

# Preview changes without modifying files
npx tailwindest-css-transform src --dry-run

# Override auto-discovered config when needed
npx tailwindest-css-transform src \
    --css src/styles/tailwind.css \
    --identifier tw \
    --module @/styles/tailwind \
    --mode runtime

Running npx tailwindest-css-transform without a target opens an interactive prompt that asks only for the file or directory to transform. The CLI then detects the Tailwind CSS entry, Tailwindest createTools export, import path, mode, walkers, and dry-run setting.

CLI Options

| Option | Alias | Default | Description | | :-------------------- | :---- | :------------- | :---------------------------------------------- | | --css <path> | -c | auto-detected | Tailwind CSS entry used to initialize Tailwind. | | --identifier <name> | -i | auto or tw | Tailwindest import identifier. | | --module <path> | -m | auto or ~/tw | Tailwindest module import path. | | --dry-run | -d | false | Preview changes without modifying files. | | --mode <mode> | - | auto | Output mode: auto or runtime. | | --help | -h | - | Display help for command. |

Auto discovery uses the same Tailwind CSS root and Tailwind package resolution helpers as create-tailwind-type. If a local Tailwind package is older than v4, the CLI warns and falls back to the internal Tailwind v4 engine.

Example

Before:

const className =
    "flex items-center justify-center p-4 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors"

After:

const style = tw.style({
    display: "flex",
    alignItems: "items-center",
    justifyContent: "justify-center",
    padding: "p-4",
    backgroundColor: "bg-blue-500",
    hover: {
        backgroundColor: "hover:bg-blue-600",
    },
    color: "text-white",
    borderRadius: "rounded-lg",
    transitionProperty: "transition-colors",
})

Preserved raw tokens

Some Tailwind tokens are not direct CSS properties but are still required for selectors, variants, animations, or CSS-variable recipes. The transformer preserves these tokens with tw.def(...) or raw tw.join(...).

Before:

const value = cn(
    "peer/menu-button flex text-sm hover:bg-sidebar-accent",
    className
)

After:

const menuButton = tw.style({
    display: "flex",
    fontSize: "text-sm",
    hover: {
        backgroundColor: "hover:bg-sidebar-accent",
    },
})

const value = tw.join(
    tw.def(["peer/menu-button"], menuButton.style()),
    className
)

This preservation path covers token families such as:

  • named group and peer anchors: group/card, peer/menu-button
  • named container anchors: @container/card-header
  • arbitrary declarations: [--card-spacing:--spacing(5)]
  • variant arbitrary declarations: data-[size=sm]:[--card-spacing:--spacing(4)]
  • placement animation utilities: data-[side=bottom]:slide-in-from-top-2
  • descendant/universal selector chains such as **:data-[slot=kbd]:z-50, focus:**:text-accent-foreground, and *:data-[slot=input-group]:...
  • parenthesized arbitrary value utilities when the active generated typeset cannot represent their variant key, such as xs:w-(--popup-width) in a project whose generated TailwindNestGroups does not include xs

The classifier uses whole-token representability, not only per-segment type membership. A variant chain containing * or ** is preserved as a raw token because translating it to nested object keys can obscure the original Tailwind selector order even when each individual segment is typeable.

Typeset-aware record keys

The transformer does not emit object keys from CSS declaration names. It asks create-tailwind-type to resolve utilities against the actual generated Tailwind interface used by Tailwindest.

// Source
className = "group-has-[[data-sidebar=menu-action]]/menu-item:pr-8"

// Output
tw.style({
    "group-has-[[data-sidebar=menu-action]]/menu-item": {
        padding: "group-has-[[data-sidebar=menu-action]]/menu-item:pr-8",
    },
})

This is intentionally padding, not paddingRight, when the generated Tailwindest typeset exposes pr-* under the padding record key. The shadcn registry typecheck gate runs generated output against the real tailwind.2.ts-style typeset so stale mock type definitions cannot hide invalid object keys.

CVA migration

cva(...) declarations are rewritten to the matching Tailwindest styler API. Static variant maps become tw.variants(...), VariantProps becomes GetVariants, and call sites use .class(...) so they match the createTools signatures.

Before:

import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const buttonVariants = cva("inline-flex items-center", {
    variants: {
        variant: {
            default: "bg-primary text-primary-foreground",
            outline: "border bg-background",
        },
        size: {
            default: "h-9 px-4",
            sm: "h-8 px-3",
        },
    },
})

interface ButtonProps extends VariantProps<typeof buttonVariants> {
    className?: string
}

function Button({ className, variant, size }: ButtonProps) {
    return (
        <button className={cn(buttonVariants({ variant, size, className }))} />
    )
}

After:

import { tw } from "~/tw"
import { type GetVariants } from "tailwindest"

const buttonVariants = tw.variants({
    base: {
        display: "inline-flex",
        alignItems: "items-center",
    },
    variants: {
        variant: {
            default: {
                backgroundColor: "bg-primary",
                color: "text-primary-foreground",
            },
            outline: {
                borderWidth: "border",
                backgroundColor: "bg-background",
            },
        },
        size: {
            default: {
                height: "h-9",
                padding: "px-4",
            },
            sm: {
                height: "h-8",
                padding: "px-3",
            },
        },
    },
})

interface ButtonProps extends GetVariants<typeof buttonVariants> {
    className?: string
}

function Button({ className, variant, size }: ButtonProps) {
    return (
        <button
            className={tw.join(
                buttonVariants.class({ variant, size }),
                className
            )}
        />
    )
}

When a cva(...) declaration has no variant map, it is emitted as tw.style(...) and call sites use .class(...).

When a cva(...) declaration contains preserved tokens, call sites use tw.def(..., helper.style(...)). Base preserved tokens are unconditional; variant-option preserved tokens are conditional on the selected option:

const value = tw.join(
    tw.def(
        [
            "peer/menu-button",
            variant === "outline" && "hover:bg-sidebar-accent",
        ],
        sidebarMenuButtonVariants.style({ variant })
    ),
    className
)

If a call site does not expose a safe selected variant value, the transformer does not guess. It emits a diagnostic instead of unconditionally applying a variant-specific token.

Preservation Model

The transformer first builds a lossless plan for each supported static class source:

  • structured tokens: resolver-backed utilities emitted into Tailwindest style objects
  • preserved tokens: unresolved or non-property tokens emitted as class literals

Every supported static input token must be represented in the generated output. Exact class order is not treated as a universal guarantee, but dynamic/user class arguments keep their later precedence, and static-after-dynamic cn(...) shapes are not pooled across dynamic arguments.

The shadcn registry test suite enforces this with:

  • twMerge(source) === source stability checks for every collected static source
  • transformed-output multiset checks that every input token is present
  • generated .tsx typechecks against the actual Tailwindest generated Tailwind record key surface
  • targeted historical assertions for group, peer, container, arbitrary declaration, animation, descendant-chain, and parenthesized arbitrary-value families

For more details, visit our official documentation.