Skip to content

Lightweight helpers to compose class names and inline styles using "variants". Zero runtime deps, small bundle, and first-class TypeScript support.

License

Notifications You must be signed in to change notification settings

timphandev/css-variants

css-variants

Zero-dependency, type-safe CSS variant composition for modern JavaScript

Build powerful, flexible component style systems with variants.
Perfect for Tailwind CSS, vanilla CSS, or any CSS-in-JS solution.

test npm version Bundle Size TypeScript License: MIT

Documentation · Getting Started · API Reference


Why css-variants?

Feature
~1KB — Zero dependencies, tree-shakeable
🔒 Type-Safe — Full TypeScript inference for variants & props
🧩 Flexible — Works with Tailwind, CSS Modules, vanilla CSS, inline styles
🚀 Fast — Up to 10x faster than alternatives in complex scenarios

Installation

npm install css-variants

Quick Start

Single-Element Variants (cv)

import { cv } from 'css-variants'

const button = cv({
  base: 'font-semibold rounded-lg 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',
    },
    size: {
      sm: 'px-3 py-1.5 text-sm',
      lg: 'px-6 py-3 text-lg',
    },
  },
  defaultVariants: { color: 'primary', size: 'sm' },
})

button()                              // Primary + Small (defaults)
button({ color: 'secondary' })        // Secondary + Small
button({ size: 'lg', className: 'w-full' }) // Primary + Large + custom class

Multi-Element Components (scv)

import { scv } from 'css-variants'

const card = scv({
  slots: ['root', 'header', 'body', 'footer'],
  base: {
    root: 'rounded-xl border shadow-sm',
    header: 'px-6 py-4 border-b',
    body: 'px-6 py-4',
    footer: 'px-6 py-3 bg-gray-50',
  },
  variants: {
    variant: {
      default: { root: 'bg-white border-gray-200' },
      danger: { root: 'bg-red-50 border-red-200', header: 'text-red-900' },
    },
  },
})

const styles = card({ variant: 'danger' })
// styles.root   → 'rounded-xl border shadow-sm bg-red-50 border-red-200'
// styles.header → 'px-6 py-4 border-b text-red-900'

Compound Variants

Apply styles when multiple variants match:

const button = cv({
  variants: {
    color: { primary: '...', danger: '...' },
    size: { sm: '...', lg: '...' },
  },
  compoundVariants: [
    { color: 'danger', size: 'lg', className: 'font-bold uppercase' },
  ],
})

Style Variants (sv)

For inline styles instead of class names:

import { sv } from 'css-variants'

const box = sv({
  base: { display: 'flex', borderRadius: '8px' },
  variants: {
    size: {
      sm: { padding: '8px' },
      lg: { padding: '24px' },
    },
  },
})

box({ size: 'lg' }) // => { display: 'flex', borderRadius: '8px', padding: '24px' }

Performance

vs cva vs tailwind-variants
3-4x faster (compound variants) 5-6x faster (compound variants)
4-9x faster (complex components) 10-11x faster (complex components)

Documentation

Full documentation →

Contributing

git clone https://github.com/timphandev/css-variants.git
cd css-variants && yarn install
yarn test && yarn build

License

MIT © Tim Phan

About

Lightweight helpers to compose class names and inline styles using "variants". Zero runtime deps, small bundle, and first-class TypeScript support.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks