css-variants is no longer maintained. Use @lynstack/class-recipe: faster, stricter types, and React Native support. Migrate →
No longer maintained. Migrate to class-recipe →
css-variants is no longer maintained. Use @lynstack/class-recipe: faster, stricter types, and React Native support. Migrate →
No longer maintained. Migrate to class-recipe →
css-variants is designed to be fast and lightweight. Here’s what you need to know about performance.
css-variants is extremely lightweight:
Each function can be imported independently:
// Only imports cv (~400 bytes)import { cv } from 'css-variants/cv'
// Only imports scv (~600 bytes)import { scv } from 'css-variants/scv'
// Only imports cx (~200 bytes)import { cx } from 'css-variants/scv'
// Import multipleimport { cv, cx } from 'css-variants'Variant functions are created once and reused:
// ✅ Good: Created once at module levelconst button = cv({ base: 'rounded font-medium', variants: { /* ... */ },})
function Button(props) { return <button className={button(props)} />}// ❌ Bad: Recreated on every renderfunction Button(props) { const button = cv({ base: 'rounded font-medium', variants: { /* ... */ }, }) return <button className={button(props)} />}When you call a variant function:
For typical usage with 3-5 variants and 2-3 compound rules, this is extremely fast.
Each compound variant is checked at runtime:
// More compound variants = more checkscompoundVariants: [ { size: 'lg', color: 'primary', className: '...' }, { size: 'lg', color: 'secondary', className: '...' }, { size: 'md', color: 'primary', className: '...' }, // ... many more]Consider restructuring if you have many compound variants:
// Better: Use a dedicated variant insteadvariants: { featured: { true: 'shadow-lg font-bold', false: '', },}// ❌ Bad: Creates new variant on each callfunction getButtonVariant(theme: 'light' | 'dark') { return cv({ base: theme === 'dark' ? 'bg-gray-800' : 'bg-white', variants: { /* ... */ }, })}// ✅ Good: Handle themes within variantsconst button = cv({ variants: { theme: { light: 'bg-white text-gray-900', dark: 'bg-gray-800 text-white', }, // ... other variants },})scv creates an object of class strings for each slot. Only use it when you have multiple related elements:
// ✅ Good: Multiple related elementsconst card = scv({ slots: ['root', 'header', 'content', 'footer'], // ...})
// ❌ Overkill: Single elementconst button = scv({ slots: ['root'], // Just use cv() instead // ...})For components that render frequently with the same props, consider memoization:
import { useMemo } from 'react'
function ExpensiveComponent({ color, size, ...props }) { const className = useMemo( () => button({ color, size }), [color, size] )
return <button className={className} {...props} />}However, for most cases this is unnecessary - variant resolution is fast enough.
Variant definitions are plain JavaScript objects and have minimal memory overhead. The main memory considerations:
For typical applications with dozens of component variants, memory usage is negligible.
css-variants works identically on server and client:
This makes it ideal for SSR frameworks like Next.js, Nuxt, and Remix.
cv for single elements, scv for multi-element components