classcat, classnames, and clsx are lightweight utility libraries designed to help developers conditionally combine CSS class names in JavaScript applications. They simplify the process of dynamically building class strings by accepting a mix of strings, arrays, and objects — where object keys represent class names and boolean values determine whether those classes should be included. This avoids manual string concatenation and reduces boilerplate when working with component-based UI frameworks like React, Vue, or Svelte.
When building dynamic user interfaces, you often need to toggle CSS classes based on state, props, or context. Manually concatenating strings is error-prone and hard to read. That’s where classcat, classnames, and clsx come in — they provide clean, declarative ways to compose class names. While they solve the same problem, their APIs, performance characteristics, and design philosophies differ in subtle but important ways.
All three accept strings, arrays, and objects, but they handle nesting and falsy values differently.
classnames is the most permissive. It recursively flattens nested arrays and ignores falsy values (null, undefined, false, 0, '').
// classnames
import classNames from 'classnames';
const btnClass = classNames(
'btn',
{ primary: true, disabled: false },
['extra', null, 'padding'],
undefined
);
// → 'btn primary extra padding'
clsx mimics classnames’s behavior almost exactly but skips deep recursion for performance. It flattens only one level of arrays and still ignores falsy values.
// clsx
import clsx from 'clsx';
const btnClass = clsx(
'btn',
{ primary: true, disabled: false },
['extra', null, 'padding'],
undefined
);
// → 'btn primary extra padding'
classcat uses a stricter, array-only top-level interface. You must pass all arguments inside a single array. It does not support multiple top-level arguments or deep nesting.
// classcat
import cc from 'classcat';
const btnClass = cc([
'btn',
{ primary: true, disabled: false },
['extra', 'padding'] // nested arrays are NOT flattened
]);
// → 'btn primary extra padding' ✅
// But this fails to flatten deeper:
cc(['a', [['b']]]); // → 'a b' in classnames/clsx, but 'a [object Object]' in classcat ❌
💡 Note:
classcatdoes not flatten nested arrays beyond one level. Passing[['b']]results in unintended output.
classcat is the fastest because it avoids recursion and uses a simple loop. It’s optimized for the common case: a flat list of strings and objects.
clsx is nearly as fast as classcat but maintains API parity with classnames for shallow inputs. It avoids the overhead of deep flattening, making it ideal for typical React component use cases.
classnames is the slowest due to its recursive flattening logic, which walks through arbitrarily deep arrays. This is rarely needed in practice and adds unnecessary cost.
In real-world apps, you’ll rarely notice the difference — but in hot paths (e.g., rendering thousands of list items), clsx or classcat can reduce CPU time.
classnames has the most familiar syntax. Many developers already know it from tutorials or legacy codebases. Its flexibility feels “safe” — you can throw anything at it, and it usually works.
// Feels natural to many
<button className={classNames('btn', props.className, { active: isActive })}>
clsx offers identical DX with better performance. If your team already uses classnames, switching to clsx is often a drop-in replacement.
// Same as above, but faster
<button className={clsx('btn', props.className, { active: isActive })}>
classcat requires wrapping everything in an array, which some find less ergonomic:
// More verbose at call site
<button className={cc(['btn', props.className, { active: isActive }])}>
This can feel unnatural if you’re used to variadic functions. However, it enforces consistency and makes the input structure explicit.
All three ignore false, null, undefined, 0, and empty strings. But they diverge on edge inputs:
classnames and clsx ignore 0 but include other numbers (e.g., 42 becomes '42'). classcat behaves the same.classnames fully flattens them. clsx flattens one level; classcat does not flatten nested arrays at all.{ foo: 'bar' } includes 'foo'; { foo: '' } excludes it.// All produce 'btn 42'
classNames('btn', 42);
clsx('btn', 42);
cc(['btn', 42]);
// Only classnames fully flattens
classNames('a', [[['b']]]); // → 'a b'
clsx('a', [[['b']]]); // → 'a [object Object]'
cc(['a', [[['b']]]]); // → 'a [object Object]'
clsx as your defaultFor most new projects — especially React apps — clsx hits the sweet spot: it’s fast, small, and reads exactly like classnames. If you’re starting fresh, there’s little reason not to choose it.
classnames for legacy or maximum compatibilityIf you’re maintaining a large codebase already using classnames, or if you rely on deep array nesting (rare), migration may not be worth it. It’s stable, well-tested, and won’t break.
classcat for extreme minimalismIf you’re building a design system, library, or performance-critical component where every byte and CPU cycle counts, and you can enforce a flat input structure, classcat delivers the leanest runtime.
| Feature | classcat | classnames | clsx |
|---|---|---|---|
| Top-level args | Single array only | Variadic | Variadic |
| Nested array support | None (shallow) | Full recursion | One level only |
| Performance | Fastest | Slowest | Very fast |
| Bundle impact | Smallest | Largest | Very small |
| DX familiarity | Lower | Highest | High |
| Drop-in replacement for classnames? | No | N/A | Yes (in most cases) |
These libraries are so small and focused that the “best” choice often comes down to team preference and project constraints. But if you’re starting today, clsx gives you the best balance of speed, clarity, and compatibility — making it the smart default for modern frontend work.
Choose clsx if you want a modern, fast, and clean alternative that matches classnames’s intuitive API while being significantly more optimized. It’s an excellent default choice for new React or Vite-based projects, offering the same developer experience as classnames but with better performance and smaller footprint, without sacrificing readability or flexibility.
Choose classcat if you prioritize minimal runtime overhead and are comfortable with a more compact, array-centric API. It’s ideal for performance-sensitive environments (like micro-frontends or design systems) where bundle size and execution speed matter, and you don’t need support for nested arrays or complex object structures beyond flat key-value pairs.
Choose classnames if you’re working on a legacy codebase or need maximum compatibility and familiarity. It’s the most widely adopted option with extensive documentation and community examples, and it handles deeply nested arrays and mixed-type inputs robustly. However, its slightly heavier implementation may be unnecessary for new projects focused on minimalism.
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