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 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.
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.
Build a
classattribute string quickly.
This module makes it easy to build a space-delimited class attribute string from an object or array of CSS class names. Just pair each class with a boolean value to add or remove them conditionally.
import cc from "classcat"
export const ToggleButton = ({ isOn, toggle }) => (
<div className="btn" onClick={() => toggle(!isOn)}>
<div
className={cc({
circle: true,
off: !isOn,
on: isOn,
})}
/>
<span className={cc({ textOff: !isOn })}>{isOn ? "ON" : "OFF"}</span>
</div>
)
Try with React, lit-html, Mithril, Superfine
npm install classcat
Or without a build step—import it right in your browser.
<script type="module">
import cc from "https://unpkg.com/classcat"
</script>
cc(names)cc(names: string | number | object | array): string
import cc from "classcat"
cc("elf") //=> "elf"
cc(["elf", "orc", "gnome"]) //=> "elf orc gnome"
cc({
elf: false,
orc: null,
gnome: undefined,
}) //=> ""
cc({
elf: true,
orc: false,
gnome: true,
}) //=> "elf gnome"
cc([
{
elf: true,
orc: false,
},
"gnome",
]) //=> "elf gnome"
npm --prefix bench start