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

tailwind-to-style

v3.3.0

Published

Runtime Tailwind CSS to inline styles converter. Zero build step, SSR support, tree-shakeable. Works with React, Vue, Svelte, vanilla JS.

Downloads

701

Readme

tailwind-to-style

📦 View on npm | 🌐 Landing Page | 🛝 Playground

npm version Build Status npm downloads bundle size license

Runtime Tailwind CSS to inline styles converter. Zero build step. SSR-ready. Tree-shakeable. Works everywhere — React, Vue, Svelte, Node.js, vanilla JS.


Table of Contents


Why tailwind-to-style?

| Feature | Description | |---|---| | Zero Build Step | No PostCSS, no compilation — just JavaScript | | Framework Agnostic | React, Vue, Svelte, vanilla JS | | Full Tailwind Support | All utilities, responsive, pseudo-states, arbitrary values | | SCSS-like Nesting | twsx() for complex nested selector-based styles | | Variant System | Type-safe component variants like CVA/tailwind-variants | | Unified CSS-in-JS | twsxClassName() — one API for basic/variants/slots | | Atomic CSS Classes | tw() — reusable classes with hover/responsive support | | Conditional Classes | Built-in cx() utility (like clsx/classnames) | | SSR Support | Server-side rendering with startSSR()/stopSSR() | | @css Directive | Inject raw CSS for vendor-specific or complex properties | | Customizable | Extend theme with colors, spacing, fonts, plugins | | TypeScript | Full type definitions with generics for autocomplete | | Tree-Shakeable | Import only what you need — reduce bundle by 50-70% | | Lightweight | ~12KB minified, zero runtime dependencies | | Lightning Fast | Pre-compiled regex + multi-level LRU caching |


Installation

npm install tailwind-to-style
yarn add tailwind-to-style
pnpm add tailwind-to-style

CDN (browser):

<script src="https://unpkg.com/tailwind-to-style"></script>
<script>
  const { tws, twsx } = tailwindToStyle
</script>

Quick Start

import { tws, twsx, twsxVariants, cx } from 'tailwind-to-style'

// 1. Inline styles
const style = tws('bg-blue-500 text-white p-4 rounded-lg', true)
// → { backgroundColor: '#3b82f6', color: '#fff', padding: '1rem', borderRadius: '0.5rem' }

// 2. Real CSS with selectors
twsx({
  '.card': ['bg-white p-6 rounded-xl shadow-md', {
    '&:hover': 'shadow-xl',
    '> .title': 'text-xl font-bold text-gray-900',
  }]
})
// → auto-injects <style> with .card { ... } .card:hover { ... }

// 3. Component variants
const btn = twsxVariants('.btn', {
  base: 'px-4 py-2 rounded-lg font-medium',
  variants: {
    color: { primary: 'bg-blue-500 text-white', danger: 'bg-red-500 text-white' },
    size:  { sm: 'text-sm', md: 'text-base', lg: 'text-lg' },
  },
  defaultVariants: { color: 'primary', size: 'md' },
})
btn({ color: 'danger', size: 'lg' })  // → "btn btn-danger-lg"

// 4. Conditional classes
cx('p-4', isActive && 'bg-blue-500', { 'opacity-50': isDisabled })
// → 'p-4 bg-blue-500'

Core API

tws() — Tailwind to Inline Styles

Converts Tailwind CSS class strings into CSS string or JSON style objects at runtime.

import { tws } from 'tailwind-to-style'

// CSS string (default)
tws('bg-blue-500 p-4 rounded-lg')
// → "background-color: #3b82f6; padding: 1rem; border-radius: 0.5rem;"

// JSON object (pass `true` as 2nd argument)
tws('flex items-center gap-4', true)
// → { display: 'flex', alignItems: 'center', gap: '1rem' }

Supported features:

// Responsive breakpoints
tws('text-sm md:text-base lg:text-lg')

// Pseudo-states
tws('bg-blue-500 hover:bg-blue-600 focus:ring-2')

// Arbitrary values
tws('w-[123px] text-[#abc] mt-[2.5rem] grid-cols-[1fr,2fr]')

// Important modifier
tws('!bg-red-500 !text-white')

// Negative values
tws('-mt-4 -translate-x-2')

// Opacity modifier
tws('bg-blue-500/50 text-black/75')

// Decimal spacing
tws('p-0.5 m-1.5 gap-2.5')

Use in React:

<div style={tws('flex items-center gap-4 bg-white p-6 rounded-xl shadow-md', true)}>
  <h1 style={tws('text-2xl font-bold text-gray-900', true)}>Hello</h1>
</div>

twsx() — CSS-in-JS Engine

Generates real CSS from Tailwind classes with full selector support, SCSS-like nesting, and auto-injects a <style> tag into the DOM.

HMR-safe — each twsx() call owns a stable slot in the injected style tag keyed by its top-level selectors. When you edit styles during development, the old slot is replaced (not appended), so changes are reflected immediately without a hard refresh.

import { twsx } from 'tailwind-to-style'

twsx({
  '.button': [
    'bg-blue-500 text-white px-6 py-3 rounded-lg font-medium transition-all',
    {
      '&:hover': 'bg-blue-600 shadow-lg transform scale-105',
      '&:active': 'bg-blue-700 scale-95',
      '&:disabled': 'bg-gray-400 opacity-50 cursor-not-allowed',
      '&.large': 'px-8 py-4 text-lg',
    }
  ],

  '.card': 'bg-white rounded-xl shadow-lg overflow-hidden',
  '.card > .header': 'p-6 border-b border-gray-200',
  '.card > .body': 'p-6',

  // Media queries
  '@media (max-width: 768px)': {
    '.card': 'rounded-lg',
    '.card > .header': 'p-4',
  }
})

Nesting syntax:

| Pattern | Example | Description | |---|---|---| | &:pseudo | '&:hover': 'bg-blue-600' | Pseudo-classes | | &.modifier | '&.active': 'ring-2' | Class modifiers | | > .child | '> .title': 'text-xl' | Direct children | | .descendant | '.icon': 'w-5 h-5' | Descendants | | @media | '@media (max-width: 768px)': { ... } | Media queries |

Options:

// Disable auto-injection (returns CSS string only)
const css = twsx({ '.btn': 'bg-blue-500 text-white' }, { inject: false })

@css Directive — Raw CSS Escape Hatch

For CSS that Tailwind can't express, use the @css directive:

String form:

twsx({
  '.gradient-text': '@css { background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }',
})

Object form (within arrays):

twsx({
  '.gradient-text': [
    'text-3xl font-bold',
    {
      '@css': {
        'background': 'linear-gradient(90deg, #ff6b6b, #feca57)',
        '-webkit-background-clip': 'text',
        '-webkit-text-fill-color': 'transparent',
      },
    },
  ],
})

twsxVariants() — Component Variant System

A CVA-like API for building type-safe component variants. Auto-generates CSS for all combinations and returns a class name builder function.

import { twsxVariants } from 'tailwind-to-style'

const btn = twsxVariants('.btn', {
  base: 'px-4 py-2 rounded-lg font-medium transition-all border',
  variants: {
    variant: {
      solid: 'shadow-sm',
      outline: 'bg-transparent border-2',
      ghost: 'bg-transparent border-transparent',
    },
    color: {
      primary: 'bg-blue-500 text-white border-blue-500',
      danger: 'bg-red-500 text-white border-red-500',
      neutral: 'bg-gray-100 text-gray-900 border-gray-300',
    },
    size: {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    },
    disabled: {
      true: 'opacity-50 cursor-not-allowed pointer-events-none',
    },
  },
  compoundVariants: [
    { variant: 'outline', color: 'primary', class: 'bg-transparent text-blue-600 border-blue-500' },
    { variant: 'outline', color: 'danger', class: 'bg-transparent text-red-600 border-red-500' },
  ],
  defaultVariants: { variant: 'solid', color: 'primary', size: 'md' },
})

// Usage — returns class name string
btn()                                    // "btn"
btn({ color: 'danger' })                 // "btn btn-danger"
btn({ variant: 'outline', size: 'lg' })  // "btn btn-outline-lg"

Nested selectors — style child elements:

const alert = twsxVariants('.alert', {
  base: 'p-4 rounded-lg border flex gap-3',
  variants: {
    status: {
      info: 'bg-blue-50 border-blue-200 text-blue-800',
      error: 'bg-red-50 border-red-200 text-red-800',
    },
  },
  defaultVariants: { status: 'info' },
  nested: {
    '.alert-icon': 'flex-shrink-0 mt-0.5',
    '.alert-content': 'flex-1',
    '.alert-dismiss': 'p-1 rounded hover:bg-black/10 cursor-pointer',
  }
})
// Generates: .alert .alert-icon { ... }, .alert .alert-content { ... }, etc.

Class naming convention:

| Call | Returns | Why | |---|---|---| | btn() | "btn" | All defaults | | btn({ color: 'danger' }) | "btn btn-danger" | One non-default | | btn({ variant: 'outline', color: 'danger', size: 'lg' }) | "btn btn-outline-danger-lg" | All non-defaults |

TypeScript — full generics support:

import { twsxVariants, type VariantProps } from 'tailwind-to-style'

const button = twsxVariants('.btn', {
  base: 'px-4 py-2 rounded',
  variants: {
    variant: { solid: 'bg-blue-500', outline: 'border-2' },
    size: { sm: 'text-sm', md: 'text-base', lg: 'text-lg' },
  },
  defaultVariants: { variant: 'solid', size: 'md' },
})

type ButtonProps = VariantProps<typeof button>
// → { variant?: 'solid' | 'outline', size?: 'sm' | 'md' | 'lg' }

cx() — Conditional Class Builder

A built-in utility for conditionally joining class names — replaces clsx/classnames:

import { cx } from 'tailwind-to-style'

// Strings
cx('bg-blue-500', 'text-white')
// → 'bg-blue-500 text-white'

// Conditionals
cx('p-4', isActive && 'bg-blue-500', isDisabled && 'opacity-50')
// → 'p-4 bg-blue-500'

// Object syntax
cx('p-4', { 'bg-blue-500': isActive, 'opacity-50': isDisabled })
// → 'p-4 bg-blue-500'

// Arrays
cx(['p-4', 'bg-white'], isActive && ['ring-2', 'ring-blue-500'])
// → 'p-4 bg-white ring-2 ring-blue-500'

// Combined with tws()
const styles = tws(cx('p-4', isLarge && 'p-8', { 'bg-blue-500': isPrimary }))

cx.with() — Base class factory:

const btnClass = cx.with('px-4 py-2 rounded font-medium transition-colors')

btnClass('bg-blue-500 text-white')
// → 'px-4 py-2 rounded font-medium transition-colors bg-blue-500 text-white'

btnClass({ 'opacity-50': disabled })
// → 'px-4 py-2 rounded font-medium transition-colors opacity-50'

twsxClassName() — Unified CSS-in-JS

The complete CSS-in-JS solution with automatic mode detection. Generates scoped class names with auto-injected CSS from Tailwind classes.

import { twsxClassName, tw } from 'tailwind-to-style'

Basic Mode — Simple className generation:

// Returns: "btn-a1b2c3d4" (or "btn" if hash: false)
const button = twsxClassName({
  name: 'btn',
  _: 'px-4 py-2 bg-blue-500 text-white rounded-lg',
  hover: 'bg-blue-600',
  focus: 'ring-2 ring-blue-500',
  active: 'bg-blue-700',
  dark: 'bg-blue-400',
})

// Usage
<button class="${button}">Click me</button>

Variants Mode — Component variants like CVA/tailwind-variants:

const button = twsxClassName({
  name: 'btn',
  base: 'px-4 py-2 font-medium rounded-lg transition-colors',
  variants: {
    variant: {
      primary: 'bg-blue-500 text-white hover:bg-blue-600',
      secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
      outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
    },
    size: {
      sm: 'text-sm px-3 py-1.5',
      md: 'text-base px-4 py-2',
      lg: 'text-lg px-6 py-3',
    },
    disabled: {
      true: 'opacity-50 cursor-not-allowed',
      false: 'cursor-pointer',
    }
  },
  compoundVariants: [
    { variant: 'primary', size: 'lg', class: 'shadow-lg' },
  ],
  defaultVariants: {
    variant: 'primary',
    size: 'md',
    disabled: false,
  },
})

// Usage
<button class="${button()}">Default</button>
<button class="${button({ variant: 'secondary', size: 'lg' })}">Large Secondary</button>
<button class="${button({ disabled: true })}">Disabled</button>

Slots Mode — Multi-part components:

const card = twsxClassName({
  name: 'card',
  slots: {
    root: 'bg-white rounded-xl shadow-lg overflow-hidden',
    header: 'px-6 py-4 border-b border-gray-200',
    title: 'text-xl font-bold text-gray-900',
    body: 'px-6 py-4',
    footer: 'px-6 py-4 border-t border-gray-200',
  },
  variants: {
    variant: {
      elevated: { root: 'shadow-2xl' },
      outlined: { root: 'shadow-none border-2 border-gray-200' },
    },
  },
})

// Usage
const classes = card({ variant: 'elevated' })
<div class="${classes.root}">
  <div class="${classes.header}">
    <h2 class="${classes.title}">Card Title</h2>
  </div>
  <div class="${classes.body}">Content here</div>
  <div class="${classes.footer}">Footer</div>
</div>

Namespace Methods:

| Category | Method | Description | |----------|--------|-------------| | Config | config(options) | Set global options (prefix, hash, hashLength) | | | getConfig() | Get current configuration | | Tokens | defineTokens(tokens) | Define design tokens | | | getTokens() | Get all defined tokens | | | setToken(path, value) | Set a single token value | | Themes | createTheme(name, tokens) | Create a named theme | | | setTheme(name) | Activate a theme | | | getTheme() | Get active theme name | | | getThemes() | Get all defined themes | | Extend | extend(base, extension) | Extend an existing config | | | compose(...configs) | Compose multiple configs | | | merge(...classes) | Merge class values (like cx) | | Animation | defineAnimation(name, config) | Register custom animation | | | getAnimations() | Get all registered animations | | SSR | getCSS(className) | Get CSS for a className | | | getAllCSS() | Get all generated CSS | | | extractCSS() | Extract as <style> tag | | Cache | clearCache() | Clear all caches | | | getCacheStats() | Get cache statistics | | Utility | tw(classes) | Atomic CSS class generator |

// ═══════════════════════════════════════════════════════════════
// Configuration
// ═══════════════════════════════════════════════════════════════

// Global configuration
twsxClassName.config({ prefix: 'my-app', hash: false, hashLength: 8 })
twsxClassName.getConfig()  // → { prefix: 'my-app', hash: false, ... }

// ═══════════════════════════════════════════════════════════════
// Design Tokens
// ═══════════════════════════════════════════════════════════════

// Define tokens
twsxClassName.defineTokens({
  colors: { primary: '#3b82f6', danger: '#ef4444' },
  spacing: { sm: '0.5rem', md: '1rem' },
})

// Get all tokens
twsxClassName.getTokens()  // → { colors: {...}, spacing: {...} }

// Set a single token
twsxClassName.setToken('colors.success', '#10b981')

// Use tokens in styles (use $path.to.token syntax)
const btn = twsxClassName({ _: 'bg-$colors.primary p-$spacing.md' })

// ═══════════════════════════════════════════════════════════════
// Theme System
// ═══════════════════════════════════════════════════════════════

// Create named themes
twsxClassName.createTheme('dark', { background: '#1f2937', text: '#f9fafb' })
twsxClassName.createTheme('light', { background: '#ffffff', text: '#111827' })

// Switch themes
twsxClassName.setTheme('dark')

// Get current/all themes
twsxClassName.getTheme()   // → 'dark'
twsxClassName.getThemes()  // → { dark: {...}, light: {...} }

// ═══════════════════════════════════════════════════════════════
// Extend & Compose
// ═══════════════════════════════════════════════════════════════

// Extend existing config
const iconButton = twsxClassName.extend(button, {
  base: 'p-0 aspect-square',
  variants: { size: { sm: 'w-8 h-8', md: 'w-10 h-10' } },
})

// Compose multiple configs
const combined = twsxClassName.compose(baseStyles, variants, overrides)

// Merge classes (like cx)
twsxClassName.merge('p-4', isActive && 'bg-blue-500', { 'opacity-50': disabled })

// ═══════════════════════════════════════════════════════════════
// Animations
// ═══════════════════════════════════════════════════════════════

// Define custom animation preset
twsxClassName.defineAnimation('myFade', {
  keyframes: { '0%': { opacity: 0 }, '100%': { opacity: 1 } },
  duration: '300ms',
  timing: 'ease-out',
})

// Get all registered animations
twsxClassName.getAnimations()  // → { myFade: {...}, ... }

// Use in config
const modal = twsxClassName({
  _: 'fixed inset-0',
  animation: 'myFade',  // or built-in: 'fadeIn', 'slideUp', etc.
})

// ═══════════════════════════════════════════════════════════════
// SSR Support
// ═══════════════════════════════════════════════════════════════

// Get CSS for specific className
twsxClassName.getCSS('btn-a1b2c3d4')

// Get all generated CSS
twsxClassName.getAllCSS()

// Extract as style tag (for SSR)
const styleTag = twsxClassName.extractCSS()
// → '<style data-twsx-classname>...</style>'

// ═══════════════════════════════════════════════════════════════
// Cache Management
// ═══════════════════════════════════════════════════════════════

// Clear all caches
twsxClassName.clearCache()

// Get cache statistics
twsxClassName.getCacheStats()
// → { classNameCacheSize: 42, cssCacheSize: 38, styleRegistrySize: 15 }

tw() — Atomic CSS Classes

Generates reusable atomic CSS classes from Tailwind utilities. Unlike tws() which returns inline styles, tw() returns class names with auto-injected CSS — supporting pseudo-classes and responsive breakpoints.

import { tw } from 'tailwind-to-style'
// or: import { twsxClassName } from '...' → twsxClassName.tw(...)

Basic Usage:

// Returns: "tw-flex tw-gap-3 tw-items-center"
tw('flex gap-3 items-center')

// Usage
<div class="${tw('flex gap-3 items-center')}">
  <span>Item 1</span>
  <span>Item 2</span>
</div>

With Pseudo-classes:

// Returns: "tw-bg-gray-100 tw-hover-bg-gray-200 tw-focus-ring-2"
tw('bg-gray-100 hover:bg-gray-200 focus:ring-2')

// Unlike tws(), these actually work!
<div class="${tw('opacity-0 hover:opacity-100 transition-opacity')}">
  Hover to reveal
</div>

With Responsive Breakpoints:

// Returns: "tw-flex tw-flex-col tw-md-flex-row tw-lg-gap-8"
tw('flex flex-col md:flex-row lg:gap-8')

<div class="${tw('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4')}">
  <!-- Responsive grid -->
</div>

Combining with twsxClassName:

const button = twsxClassName({
  name: 'btn',
  base: 'px-4 py-2 rounded-lg',
  variants: { color: { primary: 'bg-blue-500', danger: 'bg-red-500' } },
})

// Use tw() for layout, twsxClassName for components
<div class="${tw('flex gap-3 flex-wrap')}">
  <button class="${button({ color: 'primary' })}">Save</button>
  <button class="${button({ color: 'danger' })}">Delete</button>
</div>

When to use which:

| Function | Use Case | Example | |----------|----------|---------| | tws() | Quick inline styles, no pseudo | style="${tws('p-4 bg-blue')}" | | tw() | Reusable layout utilities | class="${tw('flex gap-3 hover:...')}" | | twsxClassName | Components with variants | class="${button({ size: 'lg' })}" |


Configuration & Plugins

configure() — Custom Theme

Extend the default Tailwind theme with custom colors, spacing, fonts, and more.

import { configure } from 'tailwind-to-style'

configure({
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',
          600: '#2563eb',
          900: '#1e3a8a',
        },
        accent: '#f59e0b',
      },
      spacing: {
        '128': '32rem',
        '144': '36rem',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
    },
  },
})

// Now use custom values
tws('bg-brand-500 text-brand-50 p-128 font-sans')

Config API:

| Function | Description | |---|---| | configure(config) | Apply custom configuration | | getConfig() | Get current configuration | | resetConfig() | Reset to defaults | | clearConfigCache() | Clear cached config lookups |

Plugin System

Create reusable plugins to extend the utility set:

import { configure, createPlugin, createUtilityPlugin } from 'tailwind-to-style'

// Simple plugin — static utilities
const textShadow = createPlugin('text-shadow', {
  utilities: {
    'text-shadow-sm': { textShadow: '0 1px 2px rgba(0,0,0,0.05)' },
    'text-shadow-md': { textShadow: '0 2px 4px rgba(0,0,0,0.1)' },
    'text-shadow-lg': { textShadow: '0 4px 8px rgba(0,0,0,0.15)' },
  },
})

// Dynamic plugin — value-based utilities
const glass = createUtilityPlugin('glass', {
  prefix: 'glass',
  values: { sm: '4px', md: '8px', lg: '16px' },
  formatter: (value) => ({
    backdropFilter: `blur(${value})`,
    backgroundColor: 'rgba(255,255,255,0.1)',
  }),
})

configure({ plugins: [textShadow, glass] })

// Now use custom utilities
tws('text-shadow-md glass-lg')

SSR (Server-Side Rendering)

Collect CSS during server-side rendering instead of injecting into the DOM:

import { startSSR, stopSSR, getSSRStyles, twsx } from 'tailwind-to-style'

// 1. Start collecting
startSSR()

// 2. Render your app (twsx() collects CSS instead of injecting)
twsx({ '.card': 'bg-white p-6 rounded-lg shadow-md' })
twsx({ '.btn': 'bg-blue-500 text-white px-4 py-2 rounded' })
const html = renderToString(<App />)

// 3. Get collected CSS
const css = stopSSR()

// 4. Inject into HTML response
const fullHtml = `
  <html>
    <head><style>${css}</style></head>
    <body>${html}</body>
  </html>
`

SSR API:

| Function | Description | |---|---| | startSSR() | Begin collecting CSS | | stopSSR() | Stop collecting, return all CSS as string | | getSSRStyles() | Peek at collected CSS without stopping | | IS_BROWSER | true in browser environment | | IS_SERVER | true in Node.js/server environment |

Advanced SSR Collector

For more control over SSR CSS collection:

import { createSSRCollector } from 'tailwind-to-style'

// Create collector with options
const ssr = createSSRCollector({
  dedupe: true,   // Remove duplicate rules
  minify: true,   // Minify output CSS
  sort: true,     // Sort by specificity
})

// Render app
const html = renderToString(<App />)

// Extract CSS
const css = ssr.extractRaw()                    // Raw CSS string
const styleTag = ssr.extract({ id: 'ssr-css' }) // <style id="ssr-css">...</style>

// Or extract critical CSS (above-the-fold)
const { critical, rest, stats } = ssr.extractCritical({ maxSize: 14000 })
// critical: CSS under 14KB limit
// rest: remaining CSS to lazy-load
// stats: { criticalSize, criticalCount, totalCount }

// Utilities
ssr.peek()       // Preview CSS without stopping
ssr.count        // Number of rules collected
ssr.uniqueCount  // Unique rules (after dedupe)
ssr.clear(true)  // Clear and restart collecting
ssr.getStats()   // { ruleCount, uniqueCount, totalSize, minifiedSize }

Animation System

Built-in CSS Animations

Tailwind animation utilities work out of the box:

tws('animate-spin')    // → animation: spin 1s linear infinite
tws('animate-bounce')  // → animation: bounce 1s infinite
tws('animate-pulse')   // → animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite
tws('animate-ping')    // → animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite

Programmatic Animations (Web Animations API)

For interactive animations with full control:

import { animate, chain, stagger, parallel, transition } from 'tailwind-to-style'

// Single element animation
const ctrl = animate(element, 'fadeIn', { duration: 300 })
ctrl.pause()   // Pause
ctrl.play()    // Resume
ctrl.reverse() // Reverse
ctrl.cancel()  // Cancel
await ctrl.finished // Wait for completion

// Sequential animations
await chain(element, ['fadeIn', 'slideUp', 'pulse'])

// With custom options per step
await chain(element, [
  'fadeIn',
  { name: 'slideUp', delay: 200 },
  { name: 'pulse', options: { iterations: 2 } }
])

// Staggered animations (lists, grids)
stagger('.card', 'fadeIn', {
  delay: 50,           // 50ms between each
  from: 'start',       // 'start' | 'end' | 'center' | 'random'
  onAllComplete: () => console.log('Done!')
})

// Parallel animations on multiple elements
await parallel([el1, el2, el3], 'fadeIn')

// Transition between states
transition(element, 
  { opacity: 0, transform: 'scale(0.9)' },  // from
  { opacity: 1, transform: 'scale(1)' },    // to
  { duration: 300, easing: 'ease-out' }
)

Animation Presets

import { ANIMATION_PRESETS, EASING } from 'tailwind-to-style'

// Available presets
// fadeIn, fadeOut, slideUp, slideDown, slideLeft, slideRight,
// zoomIn, zoomOut, bounce, shake, pulse, spin, flipX, flipY,
// enterScale, exitScale, wiggle, heartbeat

// Easing presets
// linear, ease, easeIn, easeOut, easeInOut,
// spring, springLight, springMedium, springHeavy,
// smooth, smoothIn, smoothOut, bounce, elastic

Custom Keyframes

import { createKeyframes, clearKeyframes, registerPreset } from 'tailwind-to-style'

// Create custom keyframes (auto-injects CSS)
const animationValue = createKeyframes('myFade', {
  '0%': { opacity: 0, transform: 'translateY(-10px)' },
  '100%': { opacity: 1, transform: 'translateY(0)' }
}, { duration: 400, easing: 'ease-out' })
// → "myFade 400ms ease-out forwards"

// Register as reusable preset
registerPreset('myFade', {
  keyframes: [{ opacity: 0 }, { opacity: 1 }],
  options: { duration: 400 }
})

// Use it
animate(element, 'myFade')

// Cleanup
clearKeyframes()

Animation Utilities

import { 
  cancelAllAnimations,
  getActiveAnimationCount, 
  isAnimationSupported,
  getPresetNames
} from 'tailwind-to-style'

cancelAllAnimations()         // Stop all running animations
getActiveAnimationCount()     // Number of active animations
isAnimationSupported()        // Check Web Animations API support
getPresetNames()              // List all available preset names

Tree-Shakeable Imports

Import only what you need to reduce bundle size by 50-70%:

// Individual imports (recommended for production)
import { tws } from 'tailwind-to-style/tws'                         // ~3KB
import { twsx } from 'tailwind-to-style/twsx'                        // ~6KB
import { twsxVariants } from 'tailwind-to-style/twsx-variants'       // ~6KB
import { twsxClassName, tw } from 'tailwind-to-style/classname'      // ~8KB
import { cx } from 'tailwind-to-style/cx'                            // <1KB

// Full import (everything)
import { tws, twsx, twsxVariants, twsxClassName, cx } from 'tailwind-to-style' // ~15KB

| Import Path | Includes | Size (minified) | |---|---|---| | tailwind-to-style | Everything | ~15KB | | tailwind-to-style/tws | tws() only | ~3KB | | tailwind-to-style/twsx | twsx() | ~6KB | | tailwind-to-style/twsx-variants | twsxVariants() | ~6KB | | tailwind-to-style/classname | twsxClassName(), tw() | ~8KB | | tailwind-to-style/cx | cx() | <1KB | | tailwind-to-style/utils | Logger, LRUCache, error handler | ~2KB |

All sub-paths provide ESM + CJS bundles with TypeScript type definitions.

Utility Exports

import { 
  // Debounced versions (for rapid updates)
  debouncedTws,   // Debounced tws() - 50ms delay
  debouncedTwsx,  // Debounced twsx() - 100ms delay
  
  // Performance utilities
  performanceUtils,
  
  // Error handling
  TwsError,
  onError,
  handleError,
  
  // Cache utilities
  LRUCache,
  getTailwindCache,
  resetTailwindCache,
  
  // Logging
  logger,
  Logger,
} from 'tailwind-to-style'

// Debounced functions - useful for user input
const handleInput = (value) => {
  debouncedTws(`w-[${value}px]`, true)  // Won't spam during typing
}

// Error handling
const unsubscribe = onError((error) => {
  console.error('TWS Error:', error.message, error.context)
  sendToErrorTracking(error)
})

// Custom error
const error = handleError(new Error('Invalid class'), { className: 'invalid' })
// → TwsError with context and timestamp

// LRU Cache (bounded Map)
const cache = new LRUCache(1000)  // Max 1000 items
cache.set('key', 'value')
cache.get('key')  // → 'value'
cache.has('key')  // → true
cache.size        // → 1

// Logger
logger.setLevel('debug')  // 'debug' | 'info' | 'warn' | 'error' | 'silent'
logger.debug('Processing class:', className)

Preflight CSS

For best results, import Tailwind's preflight (base/reset styles):

import 'tailwind-to-style/preflight.css'
<!-- Or in HTML -->
<link rel="stylesheet" href="node_modules/tailwind-to-style/preflight.css">

Provides consistent box-sizing, reset margins/paddings, normalized form elements, and better default font rendering. Skip this if you're already using Tailwind CSS in your project.


Framework Integration

React

import { tws, twsx } from 'tailwind-to-style'
import { useEffect } from 'react'

function App() {
  // Inject CSS on mount
  useEffect(() => {
    twsx({
      '.card': ['bg-white rounded-xl shadow-md p-6', {
        '&:hover': 'shadow-xl',
        '> .title': 'text-xl font-bold',
      }]
    })
  }, [])

  return (
    <div style={tws('flex items-center gap-4', true)}>
      <button style={tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)}>
        Click me
      </button>
    </div>
  )
}

Vue

<script setup>
import 'tailwind-to-style/preflight.css'
import { tws, twsx } from 'tailwind-to-style'

// twsx() at the top level of <script setup> is HMR-safe.
// When you edit the classes, Vite's HMR re-runs this block and
// the old CSS slot is replaced automatically — no hard refresh needed.
twsx({
  'html': 'bg-gray-100 min-h-screen flex items-center justify-center',
  '.card': [
    'bg-white p-5 border border-gray-300 rounded-xl shadow-md',
    {
      '&:hover': 'shadow-xl',
      '> .title': 'text-xl font-bold text-gray-900 mb-3',
      '> .body': 'text-sm text-gray-700',
    },
  ],
})
</script>

<template>
  <div class="card">
    <div class="title">Card Title</div>
    <p class="body">Styled with twsx — hot reload works out of the box.</p>
  </div>
</template>

For simple inline styles, use tws() with the reactive system:

<script setup>
import { tws } from 'tailwind-to-style'
const btnStyle = tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)
</script>

<template>
  <button :style="btnStyle">Click me</button>
</template>

Svelte

<script>
  import { tws } from 'tailwind-to-style'
  const style = tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true)
</script>

<button style={Object.entries(style).map(([k,v]) => `${k}:${v}`).join(';')}>
  Click me
</button>

Vanilla JS

import { tws, twsx } from 'tailwind-to-style'

// Inline styles
const el = document.createElement('button')
Object.assign(el.style, tws('bg-blue-500 text-white px-4 py-2 rounded-lg', true))

// Inject global styles
twsx({
  '.card': 'bg-white p-6 rounded-lg shadow-md',
  '.card:hover': 'shadow-xl',
})

Performance

v3.2.0 includes major performance optimizations:

  • Pre-compiled regex — compiled once at module load, reused for every call
  • Multi-level LRU caching — class resolution, CSS generation, config lookups
  • Bounded caches — Maps capped at 5,000 entries, Sets at 10,000 to prevent memory leaks
  • Slot-based CSS injection — each twsx() call owns a named slot; updates rebuild the tag instead of appending, preventing HMR accumulation
  • FNV-1a hashing — 100x faster than JSON.stringify for cache keys
Parse 10,000 classes:
  Cold:   ~12ms
  Cached: ~0.12ms  (100x faster)

Bundle sizes:
  Full import: ~12KB minified
  tws() only:  ~3KB minified
  twsx() only: ~6KB minified

Performance utilities:

import { performanceUtils } from 'tailwind-to-style'

// View cache stats
performanceUtils.getStats()

// Clear all caches
performanceUtils.clearCaches()

// Enable performance logging
performanceUtils.enablePerformanceLogging(true)

Debugging & Logging

Logging is disabled by default. Enable via environment variable or programmatically:

TWSX_LOG_LEVEL=debug npm start   # debug, info, warn, error, silent
import { logger } from 'tailwind-to-style'

logger.setLevel('debug')
console.log(logger.getLevel()) // → 'debug'

| Level | Description | |---|---| | debug | Detailed processing info | | info | General information | | warn | Performance warnings | | error | Errors only | | silent | No logging (default) |


Comparison

| Feature | tailwind-to-style | Tailwind CSS | CSS-in-JS | tailwind-variants | |---|:---:|:---:|:---:|:---:| | Build Step | None | Required | None | Required | | Bundle Size | 3-12KB | ~80KB+ | 20-40KB | ~15KB | | Runtime Styles | Yes | No | Yes | Partial | | Full Tailwind Support | Yes | Yes | No | Classes only | | SSR Support | Yes | Yes | Depends | Yes | | Variant System | Built-in | No | No | Yes | | Conditional Classes | cx() | No | No | tv() | | SCSS-like Nesting | Yes | Plugins | Yes | No | | @css Raw Injection | Yes | No | Yes | No | | Framework Agnostic | Yes | Yes | Depends | Yes | | Tree-Shaking | Yes | Partial | Yes | Yes | | TypeScript Generics | Yes | Yes | Yes | Yes | | Zero Dependencies | Yes | PostCSS | No | tailwind-merge |


Migration from v2

See MIGRATION.md for the detailed guide from v2.x to v3.x.

Quick summary:

| Status | API | |---|---| | No changes | tws(), twsx(), configure() | | New in v3.1 | twsxVariants() | | New in v3.2 | cx(), SSR, tree-shakeable imports | | Removed | styled(), tv(), useTwsx(), TwsxProvider, CLI tools |


Contributing

Contributions are welcome! Please read CONTRIBUTING.md for architecture overview, testing guidelines, and build output docs.


💖 Support

If you find this library helpful, consider supporting:

☕ Buy me a coffee


License

MIT © Bigetion


v3.2.0Changelog · Architecture · Migration Guide