
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
tailwind-to-style
Advanced tools
Runtime Tailwind CSS to inline styles converter. Zero build step, SSR support, tree-shakeable. Works with React, Vue, Svelte, vanilla JS.
๐ฆ View on npm | ๐ Landing Page | ๐ Playground
Runtime Tailwind CSS to inline styles converter. Zero build step. SSR-ready. Tree-shakeable. Works everywhere โ React, Vue, Svelte, Node.js, vanilla JS.
| 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 |
| 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 |
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>
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'
tws() โ Tailwind to Inline StylesConverts 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 EngineGenerates 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 HatchFor 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 SystemA 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 BuilderA 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'
configure() โ Custom ThemeExtend 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 |
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')
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 |
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
import { applyWebAnimation } from 'tailwind-to-style'
// Apply a named animation to a DOM element
applyWebAnimation(element, 'fadeIn')
applyWebAnimation(element, 'slideUp')
import { applyInlineAnimation, animateElement, chainAnimations, staggerAnimations } from 'tailwind-to-style'
// Single animation
applyInlineAnimation(element, 'fadeIn')
// Programmatic animation
animateElement(element, { opacity: [0, 1] }, { duration: 300 })
// Sequential chain
chainAnimations(element, ['fadeIn', 'slideUp', 'bounceIn'])
// Staggered across multiple elements
staggerAnimations('.card', 'fadeIn', { delay: 100 })
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 { cx } from 'tailwind-to-style/cx' // <1KB
// Full import (everything)
import { tws, twsx, twsxVariants, cx } from 'tailwind-to-style' // ~12KB
| Import Path | Includes | Size (minified) |
|---|---|---|
tailwind-to-style | Everything | ~12KB |
tailwind-to-style/tws | tws() only | ~3KB |
tailwind-to-style/twsx | twsx() | ~6KB |
tailwind-to-style/twsx-variants | twsxVariants() | ~6KB |
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.
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.
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>
)
}
<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>
<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>
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',
})
v3.2.0 includes major performance optimizations:
twsx() call owns a named slot; updates rebuild the tag instead of appending, preventing HMR accumulationJSON.stringify for cache keysParse 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)
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) |
| 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 |
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 |
Contributions are welcome! Please read CONTRIBUTING.md for architecture overview, testing guidelines, and build output docs.
If you find this library helpful, consider supporting:
MIT ยฉ Bigetion
v3.2.0 โ Changelog ยท Architecture ยท Migration Guide
FAQs
Runtime Tailwind CSS to inline styles converter. Zero build step, SSR support, tree-shakeable. Works with React, Vue, Svelte, vanilla JS.
The npm package tailwind-to-style receives a total of 153 weekly downloads. As such, tailwind-to-style popularity was classified as not popular.
We found that tailwind-to-style demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.