Skip to content

What is css-variants? Introduction and Overview

css-variants is a JavaScript library for managing CSS class variants with full TypeScript support. It provides a declarative API for defining style variations (color, size, state) and generates the correct CSS classes at runtime.

Definition: A CSS variant library manages conditional styling in components. Instead of writing ternary expressions or switch statements, you define named variants and the library generates the correct classes.

import { cv } from 'css-variants'
// Define a button with color and size variants
const button = cv({
base: 'px-4 py-2 rounded font-medium transition-colors',
variants: {
color: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-6 py-3',
},
},
defaultVariants: {
color: 'primary',
size: 'md',
},
})
// Use it — returns a class string
button() // => 'px-4 py-2 rounded font-medium transition-colors bg-blue-600 text-white hover:bg-blue-700 text-base'
button({ color: 'danger' }) // => '... bg-red-600 text-white hover:bg-red-700 ...'
button({ size: 'lg' }) // => '... text-lg px-6 py-3'


css-variants addresses common pain points in component styling:

  • Messy conditional class logic — Replaces scattered ternaries with a declarative API
  • Inconsistent styling patterns — Enforces consistent variant structure across components
  • Poor TypeScript support — Provides full type inference for variant props
  • Difficult maintenance — Centralizes style logic in one place
  • Performance overhead — Optimized algorithms minimize runtime cost

css-variants exports five functions for different use cases:

Use for single-element components. Returns a class name string.

import { cv } from 'css-variants'
const badge = cv({
base: 'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium',
variants: {
color: {
gray: 'bg-gray-100 text-gray-800',
blue: 'bg-blue-100 text-blue-800',
green: 'bg-green-100 text-green-800',
},
},
})
badge({ color: 'blue' }) // => 'inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800'

Use for multi-element components (cards, modals, dropdowns). Returns an object with class strings for each slot.

import { scv } from 'css-variants'
const card = scv({
slots: ['root', 'header', 'body', 'footer'],
base: {
root: 'rounded-lg border bg-white shadow-sm',
header: 'px-6 py-4 border-b font-semibold',
body: 'px-6 py-4',
footer: 'px-6 py-3 bg-gray-50 border-t',
},
variants: {
variant: {
default: { root: 'border-gray-200' },
primary: { root: 'border-blue-200', header: 'bg-blue-50' },
},
},
})
const classes = card({ variant: 'primary' })
// classes.root => 'rounded-lg border bg-white shadow-sm border-blue-200'
// classes.header => 'px-6 py-4 border-b font-semibold bg-blue-50'
// classes.body => 'px-6 py-4'
// classes.footer => 'px-6 py-3 bg-gray-50 border-t'

Use for inline CSS styles (React’s style prop). Returns a CSS style object.

import { sv } from 'css-variants'
const box = sv({
base: { display: 'flex', borderRadius: '8px' },
variants: {
size: {
sm: { padding: '8px', gap: '8px' },
lg: { padding: '24px', gap: '16px' },
},
},
})
box({ size: 'lg' }) // => { display: 'flex', borderRadius: '8px', padding: '24px', gap: '16px' }

Use for multi-element inline styles. Returns an object with style objects for each slot.

import { ssv } from 'css-variants'
const tooltip = ssv({
slots: ['root', 'arrow'],
base: {
root: { position: 'absolute', padding: '8px', borderRadius: '4px' },
arrow: { position: 'absolute', width: '8px', height: '8px' },
},
variants: {
placement: {
top: { root: { bottom: '100%' }, arrow: { top: '100%' } },
bottom: { root: { top: '100%' }, arrow: { bottom: '100%' } },
},
},
})

A lightweight alternative to clsx for conditional class merging:

import { cx } from 'css-variants'
cx('btn', 'btn-primary') // => 'btn btn-primary'
cx('btn', isActive && 'active') // => 'btn active' or 'btn'
cx('btn', { disabled: isDisabled }) // => 'btn disabled' or 'btn'
cx(['btn', 'rounded'], 'shadow') // => 'btn rounded shadow'

css-variants vs CVA (Class Variance Authority)

Section titled “css-variants vs CVA (Class Variance Authority)”

css-variants is a faster, smaller alternative to CVA with additional features:

Featurecss-variantsCVA
Bundle size~1KB~2KB
Performance3-7x fasterBaseline
Slot variantsBuilt-in (scv)Not available
Style variantsBuilt-in (sv, ssv)Not available
API stylebase propertyFirst argument

css-variants is a faster, smaller alternative to tailwind-variants:

Featurecss-variantstailwind-variants
Bundle size~1KB~5KB
Performance5-11x fasterBaseline
Dependencies01 (tailwind-merge)
Built-in tw-mergeNo (opt-in)Yes
Style variantsBuilt-inNot available

Can I Use css-variants Without Tailwind CSS?

Section titled “Can I Use css-variants Without Tailwind CSS?”

Yes. css-variants is CSS-framework agnostic and works with any CSS approach:

  • Tailwind CSS — The most common use case
  • Vanilla CSS — Regular class names like .btn, .btn-primary
  • CSS Modules — Import and use scoped class names
  • CSS-in-JS — Use with styled-components, emotion, etc.
  • Other utility frameworks — Bootstrap, Bulma, UnoCSS, etc.
// Works with vanilla CSS
const button = cv({
base: 'btn',
variants: {
variant: {
primary: 'btn-primary',
secondary: 'btn-secondary',
},
},
})
// Works with CSS Modules
import styles from './Button.module.css'
const button = cv({
base: styles.btn,
variants: {
variant: {
primary: styles.primary,
secondary: styles.secondary,
},
},
})

What Frameworks Does css-variants Support?

Section titled “What Frameworks Does css-variants Support?”

css-variants works with all JavaScript frameworks:

FrameworkSupport
ReactFull support
Vue 3Full support
SvelteFull support
Solid.jsFull support
PreactFull support
Vanilla JSFull support

See the Framework Guide for implementation examples.