classcat, classnames, and clsx are lightweight utility libraries designed to help developers conditionally join CSS class names in JavaScript applications. They solve the common problem of dynamically building a string of class names based on props, state, or logic without manual string concatenation or ternary clutter. Each offers a clean API for combining static classes with conditional ones, supporting arrays, objects, and nested structures, but they differ in syntax ergonomics, performance characteristics, and handling of edge cases.
When building dynamic UIs, we often need to toggle CSS classes based on component state, props, or theme context. Manually managing this with template literals or ternaries quickly becomes messy:
// Painful manual approach
const buttonClass =
'btn ' +
(primary ? 'btn--primary ' : '') +
(disabled ? 'btn--disabled ' : '') +
(size === 'large' ? 'btn--large' : '');
This is where classcat, classnames, and clsx come in — all three solve the same core problem but with different trade-offs in API design, performance, and predictability.
classnames accepts almost anything:
null, undefined, false, 0) are safely ignored// classnames example
import cn from 'classnames';
const classes = cn(
'base',
{ active: isActive, disabled: isDisabled },
[extraClass, { hidden: shouldHide }]
);
// → 'base active hidden' (if isActive/shouldHide are true)
clsx behaves similarly but with stricter rules:
// clsx example
import clsx from 'clsx';
const classes = clsx(
'btn',
{ 'btn--primary': primary },
size && `btn--${size}`
);
// → 'btn btn--primary btn--large'
classcat only accepts arrays:
// classcat example
import cc from 'classcat';
const classes = cc([
'btn',
primary && 'btn--primary',
size && `btn--${size}`,
disabled && 'btn--disabled'
]);
// → 'btn btn--primary btn--large'
All three are fast enough for typical use cases, but differences emerge under stress:
classcat wins in raw speed because it assumes flat arrays and does zero object inspection. No branching on input types means fewer CPU cycles.clsx is optimized for common patterns (like { foo: true }) and avoids deep recursion, making it nearly as fast as classcat in practice.classnames carries slight overhead from its permissive input handling — it recursively flattens deeply nested structures and checks every value’s truthiness.In a tight loop rendering thousands of components (e.g., data grids), classcat or clsx may show measurable gains. For most apps, the difference is negligible.
classnames feels familiar to veterans — it’s been around since the early React days and works everywhere. Its forgiving nature helps during rapid prototyping but can mask logic errors (e.g., accidentally passing 0 as a class).
clsx has become the go-to in modern React/Tailwind stacks. Its name is short, its behavior is predictable, and it plays well with TypeScript. Many UI libraries (like Radix UI) use it internally.
classcat appeals to teams that value explicitness over convenience. Because it rejects objects, you’re forced to handle conditional logic before calling it — which some consider a feature, not a limitation.
classnames and clsx both drop 0, but classnames once had quirks with numeric keys in objects (now fixed). classcat never sees this issue since it doesn’t accept objects.classnames deeply flattens [ ['a', ['b']] ] → 'a b'. clsx only flattens one level. classcat expects flat arrays and won’t recurse.clsx({ active: true }) you get 'active'. With classcat, you’d write active && 'active' — more verbose but clearer in intent.clsx. It’s concise, fast, and widely adopted.classnames. Its flexibility reduces migration friction.classcat. The enforced structure and speed matter here.For most new projects in 2024, clsx strikes the best balance: it’s fast, clean, and integrates seamlessly with modern tooling. Reserve classcat for performance-critical paths where you control all inputs, and lean on classnames only when dealing with unpredictable or legacy data shapes.
Remember — the best class composer is the one that disappears into your workflow. All three do the job; choose based on your team’s style, not hype.
Choose clsx if you want a modern balance of speed and developer experience. It supports both array and object inputs with clean syntax, strips falsy values reliably, and has become the de facto standard in newer React ecosystems (especially with Tailwind CSS). Its compact implementation and intuitive API make it a strong default choice for most contemporary frontend projects.
Choose classnames if you need maximum compatibility and support for legacy codebases. It handles a wide variety of input types—including nested arrays, objects, and falsy values—with forgiving semantics. This flexibility comes at the cost of slightly heavier internal logic, making it best suited for applications where developer convenience outweighs micro-optimizations.
Choose classcat if you prioritize minimal runtime overhead and predictable behavior with array-based inputs. It’s ideal for performance-sensitive environments like animation-heavy UIs or micro-frontends where every millisecond counts. Its strict array-only interface avoids object coercion surprises but requires more explicit structure from callers.
A tiny (239B) utility for constructing
classNamestrings conditionally.
Also serves as a faster & smaller drop-in replacement for theclassnamesmodule.
This module is available in three formats:
dist/clsx.mjsdist/clsx.jsdist/clsx.min.js$ npm install --save clsx
import clsx from 'clsx';
// or
import { clsx } from 'clsx';
// Strings (variadic)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
// Objects
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'
// Objects (variadic)
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'
// Arrays
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'
// Arrays (variadic)
clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);
//=> 'foo bar baz hello there'
// Kitchen sink (with nesting)
clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'
Returns: String
Type: Mixed
The clsx function can take any number of arguments, each of which can be an Object, Array, Boolean, or String.
Important: Any falsey values are discarded!
Standalone Boolean values are discarded as well.
clsx(true, false, '', null, undefined, 0, NaN);
//=> ''
There are multiple "versions" of clsx available, which allows you to bring only the functionality you need!
clsxSize (gzip): 239 bytes
Availability: CommonJS, ES Module, UMD
The default clsx module; see API for info.
import { clsx } from 'clsx';
// or
import clsx from 'clsx';
clsx/liteSize (gzip): 140 bytes
Availability: CommonJS, ES Module
CAUTION: Accepts ONLY string arguments!
Ideal for applications that only use the string-builder pattern.
Any non-string arguments are ignored!
import { clsx } from 'clsx/lite';
// or
import clsx from 'clsx/lite';
// string
clsx('hello', true && 'foo', false && 'bar');
// => "hello foo"
// NOTE: Any non-string input(s) ignored
clsx({ foo: true });
//=> ""
For snapshots of cross-browser results, check out the bench directory~!
All versions of Node.js are supported.
All browsers that support Array.isArray are supported (IE9+).
Note: For IE8 support and older, please install
clsx@1.0.xand beware of #17.
Here some additional (optional) steps to enable classes autocompletion using clsx with Tailwind CSS.
Install the "Tailwind CSS IntelliSense" Visual Studio Code extension
Add the following to your settings.json:
{
"tailwindCSS.experimental.classRegex": [
["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
]
}
You may find the clsx/lite module useful within Tailwind contexts. This is especially true if/when your application only composes classes in this pattern:
clsx('text-base', props.active && 'text-primary', props.className);
MIT © Luke Edwards