Skip to content

cx - Class Name Merger

A lightweight utility for merging class names. Supports strings, arrays, objects, and nested combinations.

import { cx } from 'css-variants'
function cx(...args: ClassValue[]): string
type ClassValue =
| string
| number
| bigint
| boolean
| null
| undefined
| ClassDictionary
| ClassValue[]
type ClassDictionary = Record<string, unknown>
ParameterTypeDescription
...argsClassValue[]Any number of class values to merge

Returns a single string with all valid class names joined by spaces.

import { cx } from 'css-variants'
cx('foo', 'bar')
// => 'foo bar'
cx('foo', null, 'bar', undefined, 'baz')
// => 'foo bar baz'
cx('foo', false && 'bar', 'baz')
// => 'foo baz'

Conditionally include classes based on boolean values:

cx({ foo: true, bar: false, baz: true })
// => 'foo baz'
cx('base', { active: isActive, disabled: isDisabled })
// => 'base active' (if isActive is true and isDisabled is false)

Group related classes together:

cx(['foo', 'bar'])
// => 'foo bar'
cx(['foo', null, 'bar'])
// => 'foo bar'
cx(['text-lg', 'font-bold'], 'text-blue-600')
// => 'text-lg font-bold text-blue-600'

Combine all formats as needed:

cx(
'base-class',
['array-class-1', 'array-class-2'],
{ conditional: true, ignored: false },
condition && 'conditional-class',
42,
null,
undefined
)
// => 'base-class array-class-1 array-class-2 conditional conditional-class 42'

Arrays can be nested to any depth:

cx('a', ['b', ['c', 'd']], 'e')
// => 'a b c d e'

Numbers are converted to strings:

cx('z-index-', 10)
// => 'z-index- 10'
cx(1, 2, 3)
// => '1 2 3'
function Component({ isActive, isDisabled, className }) {
return (
<div
className={cx(
'base-class',
isActive && 'active',
isDisabled && 'disabled',
className
)}
>
Content
</div>
)
}
// Usage
<Component isActive className="custom-class" />
// => <div className="base-class active custom-class">
<template>
<div :class="classes">Content</div>
</template>
<script setup>
import { computed } from 'vue'
import { cx } from 'css-variants'
const props = defineProps(['isActive', 'isDisabled'])
const classes = computed(() =>
cx(
'base-class',
{ active: props.isActive, disabled: props.isDisabled }
)
)
</script>
const buttonClass = cx(
'btn',
variant === 'primary' && 'btn-primary',
variant === 'secondary' && 'btn-secondary',
size === 'large' && 'btn-lg',
disabled && 'btn-disabled'
)
function Button({ className, ...props }) {
return (
<button
className={cx('px-4 py-2 rounded', className)}
{...props}
/>
)
}
const containerClass = cx(
'container mx-auto',
['px-4', 'sm:px-6', 'lg:px-8'],
fullWidth && 'max-w-none'
)
const inputClass = cx(
'border rounded px-3 py-2',
{
'border-gray-300 focus:border-blue-500': !error,
'border-red-500 focus:border-red-600': error,
'bg-gray-100 cursor-not-allowed': disabled,
}
)

cx is similar to clsx and classnames, but optimized for css-variants:

// css-variants cx
import { cx } from 'css-variants'
// clsx
import clsx from 'clsx'
// classnames
import classNames from 'classnames'
// All produce the same output for basic usage
cx('foo', 'bar') // => 'foo bar'
clsx('foo', 'bar') // => 'foo bar'
classNames('foo', 'bar') // => 'foo bar'

For Tailwind CSS projects, combine cx with tailwind-merge to handle class conflicts:

import { cx } from 'css-variants'
import { twMerge } from 'tailwind-merge'
// Create a custom merger
const cn: typeof cx = (...args) => twMerge(cx(...args))
// Now conflicting Tailwind classes are resolved
cn('px-4 py-2', 'px-6')
// => 'py-2 px-6' (px-4 is removed, px-6 wins)

See the Tailwind CSS Integration guide for more details.