classnames and clsx are both lightweight utility libraries designed to help developers construct conditional CSS class strings in JavaScript-based UIs. They solve the common problem of dynamically combining static and conditional classes without manual string concatenation or complex ternary logic. Both accept a mix of strings, objects, arrays, and nested structures, returning a clean space-delimited class name string suitable for use in React, Vue, or vanilla DOM APIs.
When building dynamic user interfaces, we often need to toggle CSS classes based on component state, props, or application context. Writing inline conditionals like ${isActive ? 'active' : ''} ${isLoading ? 'loading' : ''} quickly becomes messy and error-prone. That’s where classnames and clsx come in — both provide clean, declarative APIs to compose class strings safely. But under the hood, they differ in philosophy, scope, and performance characteristics.
Both libraries accept the same basic input types:
// Shared behavior example
const isActive = true;
const isLoading = false;
// Both return "btn primary active"
classnames('btn', 'primary', { active: isActive, loading: isLoading });
clsx('btn', 'primary', { active: isActive, loading: isLoading });
However, their treatment of edge cases diverges.
classnames explicitly filters out all falsy values (false, null, undefined, 0, NaN, empty string):
// Returns "visible" — skips 0 and empty string
classnames('visible', 0, '', null, undefined);
clsx behaves identically here — both treat these as non-renderable and omit them.
classnames fully flattens arbitrarily deep arrays:
// Returns "a b c d"
classnames(['a', ['b', ['c']], 'd']);
clsx only flattens one level deep by design:
// Returns "a b,c d" — inner array becomes "b,c" via .join(' ')
clsx(['a', ['b', 'c'], 'd']);
In practice, this rarely matters because most developers avoid deeply nested class arrays. But if your codebase uses recursive component composition that generates nested structures, classnames is more forgiving.
clsx is optimized for the 95% case: flat inputs with strings and simple objects. It avoids recursion and uses tight loops, making it consistently faster in benchmarks involving typical React prop patterns.
classnames, while still fast, carries slight overhead from its robust flattening logic and broader type handling. This difference is negligible in most apps but can add up in:
If you’re building a dashboard with 10k+ dynamic table rows, clsx might shave off meaningful milliseconds.
Both packages are tiny, but clsx has a simpler internal structure that plays better with modern bundlers. Its ESM build exports a single function with no side effects, enabling perfect tree-shaking even in complex dependency graphs.
classnames also supports ESM, but its historical UMD roots mean some older tooling configurations might pull in slightly more dead code. In contemporary setups (Vite, modern Webpack/Rollup), this gap has mostly closed.
Both offer identical DX for standard usage. However, when things go wrong:
classnames’s exhaustive flattening can mask bugs caused by accidentally passing nested arraysclsx’s stricter approach surfaces structural issues earlier (e.g., seeing "b,c" instead of "b c" reveals unintended nesting)Consider this debugging scenario:
// Accidentally double-wrapped array
const extraClasses = [['highlight']];
// classnames: silently works → "btn highlight"
// clsx: exposes mistake → "btn highlight" (wait, why isn't it working?)
// Actually returns "btn highlight" in both? Let's correct:
// clsx(['btn', extraClasses]) → "btn highlight" (same as classnames)
// Correction: The real difference appears with deeper nesting:
const badInput = ['btn', [['error']]];
// classnames → "btn error"
// clsx → "btn error" (still same? Actually...)
Upon closer inspection, the practical debugging difference is minimal because both handle two-level nesting correctly. The divergence only appears at three+ levels, which is uncommon. So this point is often overstated in discussions.
The APIs are so similar that switching between them usually requires only a find-and-replace of the import statement:
- import classNames from 'classnames';
+ import clsx from 'clsx';
- const className = classNames('foo', { bar: true });
+ const className = clsx('foo', { bar: true });
This near-perfect compatibility means you can evaluate either without major refactoring risk.
clsx when:classnames when:classnames-style deep flatteningFor new greenfield projects, clsx is generally the better choice — it’s faster, smaller, and sufficient for virtually all real-world use cases. Its constraints encourage cleaner data structures.
For existing applications, stick with whichever you already use unless you have specific performance bottlenecks tied to class composition. The migration effort rarely justifies the marginal gains.
Most importantly: don’t overthink it. Both solve the core problem elegantly, and your team’s consistency matters more than micro-optimizations. Pick one, enforce it via lint rules, and move on to harder problems.
Choose clsx if you prioritize a smaller runtime footprint and slightly faster execution in performance-sensitive contexts like high-frequency renders or server-side rendering at scale. Its implementation is more streamlined and optimized for the most common usage patterns (strings, flat objects, and shallow arrays), which covers 95%+ of real-world scenarios while avoiding overhead from handling rare edge cases.
Choose classnames if you're working on a mature codebase that already depends on it or if your team prefers a battle-tested solution with broad adoption across legacy and modern projects. Its API handles edge cases predictably and supports deeply nested arrays and mixed-type inputs reliably, making it a safe default for applications where stability trumps 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