Twirlwind
Tailwind v4-first CSS-to-utility-class serializer for JavaScript/TypeScript.
Converts style objects, CSS declaration strings, and CSSStyleDeclaration values into clean Tailwind utility classes. Prefers canonical utilities, falls back to arbitrary values, then arbitrary properties — every CSS property produces valid output.
Install
npm install twirlwind
Quick start
import { styleToClassName } from 'twirlwind'
styleToClassName({
display: 'flex',
justifyContent: 'center',
padding: '16px 8px',
backgroundColor: 'oklch(62.3% 0.214 259.815)'
})
API
styleToTailwind(input, options?)
Full conversion result with metadata.
import { styleToTailwind } from 'twirlwind'
const result = styleToTailwind({ display: 'flex', width: '37px' })
result.className
result.classes
result.exact
result.arbitrary
result.unmatched
result.warnings
styleToClassName(input, options?)
Returns just the class string.
styleToClassName({ padding: '16px', color: 'white' })
styleToClasses(input, options?)
Returns an array of class strings.
styleToClasses({ margin: '8px 16px' })
cssTextToTailwind(cssText, options?)
Parses a CSS declaration string.
cssTextToTailwind('display: flex; gap: 16px; padding: 8px')
Input formats
styleToClassName({ backgroundColor: 'white', 'font-size': '16px' })
styleToClassName('display: flex; padding: 16px')
styleToClassName(element.style)
styleToClassName(
new Map([
['display', 'flex'],
['padding', '16px']
])
)
Features
Color matching
Matches colors across formats to Tailwind's palette — OKLCH (v4), hex (v3), rgb(), keywords, and opacity modifiers.
styleToClassName({ color: '#ef4444' })
styleToClassName({ color: 'rgb(59 130 246)' })
styleToClassName({ color: 'oklch(62.3% 0.214 259.815)' })
styleToClassName({ color: 'oklch(62.3% 0.214 259.815 / 50%)' })
styleToClassName({ color: 'inherit' })
styleToClassName({ color: 'currentColor' })
Shorthand expansion
CSS shorthands decompose into Tailwind longhands.
styleToClassName({ border: '2px solid #ef4444' })
styleToClassName({ font: 'bold 16px/1.5 sans-serif' })
styleToClassName({ background: 'white center no-repeat' })
styleToClassName({ transition: 'all 200ms ease-in' })
Multi-value transforms and filters
Compound transform and filter declarations decompose into individual utility classes.
styleToClassName({ transform: 'translateX(8px) rotate(45deg)' })
styleToClassName({ filter: 'blur(8px) brightness(0.75)' })
Compression
Expanded longhands compress to shorthand utilities when values match.
styleToClassName({ margin: '8px' })
styleToClassName({ inset: '0' })
styleToClassName({ padding: '8px 16px' })
styleToClassName({ borderRadius: '8px' })
styleToClassName({ gap: '12px 12px' })
Variants
Nested objects map to Tailwind variants — pseudo-classes, media queries, and container queries.
styleToClassName({
color: 'white',
':hover': { color: '#3b82f6' },
'@media (min-width: 768px)': { display: 'grid' },
'@media (prefers-color-scheme: dark)': { backgroundColor: 'black' },
'@container (min-width: 512px)': { display: 'flex' }
})
Scroll snap decomposition
styleToClassName({ scrollSnapType: 'x mandatory' })
Arbitrary fallback
Every CSS property produces valid output. Unknown properties use [property:value] syntax.
styleToClassName({ scrollTimelineName: '--main' })
styleToClassName({ width: '37px' })
Options
styleToTailwind(input, {
allowArbitraryValues: true,
allowArbitraryProperties: true,
compression: 'safe',
sort: 'grouped',
colorMatch: 'exact',
numericMultipliers: 'integer',
theme: {
colors: { brand: '#ff6600' },
spacing: { '18': '4.5rem' }
}
})
How it works
- Normalize — camelCase → kebab-case, numeric → px, vendor prefixes,
!important
- Expand shorthands —
margin, border, font, background, transition, overflow, gap, etc.
- Convert — exact utility → value alias → spacing token → color match → arbitrary value → arbitrary property
- Compress — merge matching longhands back to axis/all shorthand utilities
- Sort — deterministic output ordering
License
MIT