@gentelduck/variants
TypeScript icon, indicating that this package has built-in type declarations

0.1.10 • Public • Published

@gentelduck/variants

@gentelduck/variants

A lightweight utility for generating class names based on variant configurations. Inspired by Tailwind and class-variance-authority, @gentelduck/variants helps you manage and compose class names in a flexible, type-safe, and declarative way.

Philosophy

At GentleDuck, we believe that developer tools should be fast, reliable, and actively maintained — not abandoned. While class-variance-authority initially inspired the idea of variant-based class management, its situation has become unacceptable:
despite having over 6 million weekly downloads, the project has been left poorly maintained for more than 6 months, with important pull requests and bug fixes sitting untouched.

Leaving a critical utility like this in a broken or half-maintained state is unacceptable to us at GentleDuck.
So, we took action: we rewrote the library from scratch with our own vision and philosophy — making it more modern, more reliable, and up to 7× faster.

Our goal with @gentelduck/variants is simple:
to offer the community a serious, well-maintained, and future-proof alternative — one that developers can trust today and for the years to come.


Features

  • 🧠 Declarative variant-based styling
  • 🎯 Type-safe API with intelligent autocompletion
  • 🧱 Composable and extendable utility
  • 🎨 Supports default variants and compound variants
  • 🪶 Lightweight, zero dependencies, blazing fast

Why @gentelduck/variants?

  • Zero dependencies, tiny bundle footprint.
  • 🔐 Fully type-safe, IDE-friendly autocompletion.
  • 🎨 Flexible styling via variants, nested arrays & conditionals.
  • 🚀 Minimal runtime & memoized with zero runtime dependencies (just a few dozen lines of code).
  • Blazing fast: ~7× faster than the reference (class-variance-authority)[https://www.npmjs.com/package/class-variance-authority].
  • 🧠 Powerful system for defaults, compounds, and custom classes.

Benchmark

Vitest Benchmark

@gentelduck/variants: ~7x faster than (class-variance-authority)[https://www.npmjs.com/package/class-variance-authority] Benchmark Benchmark

Duck Benchmark

Benchmark


Installation

npm install @gentelduck/variants
# or
yarn add @gentelduck/variants
# or
pnpm add @gentelduck/variants

Getting Started

@gentelduck/variants lets you declare your style variants once and get back a function that produces perfectly composed class names, complete with defaults, compounds, nested arrays, and conditional objects.

Basic Example

import { cva } from '@gentelduck/variants'

const button = cva('btn', {
  variants: {
    size: {
      sm: 'px-2 py-1 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    },
    color: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-gray-100 text-gray-800',
    },
  },
  defaultVariants: {
    size: 'md',
    color: 'primary',
  },
  compoundVariants: [
    { size: 'lg', color: 'primary', className: 'uppercase font-bold' },
  ],
})

button() 
// => 'btn px-4 py-2 text-base bg-blue-500 text-white uppercase font-bold'

button({ size: 'sm', class: ['rounded', 'shadow'] })
// => 'btn px-2 py-1 text-sm bg-blue-500 text-white rounded shadow'

Advanced Usage

Arrays & Multiple Classes

You can supply arrays of classes or nested arrays for fine-grained control:

const badge = cva('badge', {
  variants: {
    size: {
      sm: ['badge-sm', 'text-xs'],
      lg: ['badge-lg', 'text-lg'],
    },
    tone: {
      muted: ['opacity-50', ['italic']],
      loud: ['opacity-100', { 'font-bold': true }],
    },
  },
  defaultVariants: {
    size: 'sm',
    tone: 'muted',
  },
})

badge({ size: 'lg', tone: 'loud' })
// => 'badge badge-lg text-lg opacity-100 font-bold'

Compound Variants

Apply extra classes when multiple variant values match:

const card = cva('card', {
  variants: {
    size: { small: 'card-sm', large: 'card-lg' },
    color: { primary: 'card-primary', secondary: 'card-secondary' },
  },
  defaultVariants: { size: 'small', color: 'primary' },
  compoundVariants: [
    { size: 'large', color: 'primary', class: 'shadow-xl' },
    { size: 'small', color: ['secondary', 'primary'], className: 'border-dashed' },
  ],
})

card({ size: 'large', color: 'primary' })
// => 'card card-lg card-primary shadow-xl'

TypeScript Support

Built from the ground up with TypeScript:

  • Autocompletion for variant keys & values
  • Strict checks: invalid variant names/values will error at compile time
  • Utility types like VariantProps<> to extract only variant-related props
const alert = cva('alert', {
  variants: {
    severity: {
      info: 'alert-info',
      error: 'alert-error',
    },
  },
  defaultVariants: {
    severity: 'info',
  },
})

// ✅
alert({ severity: 'error' })

// ❌ TS Error: 'warning' is not a valid severity
alert({ severity: 'warning' })

Composing and Extending

Compose one CVA function with another, or extend defaults:

const baseButton = cva('btn', {
  variants: { size: { sm: 'btn-sm', lg: 'btn-lg' } },
  defaultVariants: { size: 'sm' },
})

const largePrimary = cva(baseButton, {
  defaultVariants: { size: 'lg' },
})

largePrimary({ className: 'mx-2' })
// => 'btn btn-lg mx-2'

You can also simply concatenate:

const icon = cva('icon', { variants: { type: { arrow: 'icon-arrow' } } })

`${button()} ${icon({ type: 'arrow' })}`
// => 'btn px-4 py-2 text-base bg-blue-500 text-white uppercase font-bold icon icon-arrow'

API Reference

cva(base, options) / cva(optionsWithBase)

Creates your CVA function:

type CvaFn<T> = (
  props?: VariantProps<T> & {
    class?: ClassValue
    className?: ClassValue
  }
) => string
  • base: string — always-included classes
  • variants — map of variant names → (value → class/string[])
  • defaultVariants? — fallback values
  • compoundVariants? — apply extra classes when multiple values match

Overloads:

// Two-arg form
const fn = cva('base', { variants, defaultVariants, compoundVariants })

// Single-arg form
const fn = cva({ base: 'base', variants, defaultVariants, compoundVariants })

License

MIT © GentleDuck

Package Sidebar

Install

npm i @gentelduck/variants

Weekly Downloads

99

Version

0.1.10

License

MIT

Unpacked Size

34 kB

Total Files

7

Last publish

Collaborators

  • wild-duck