clsx vs classcat vs classnames
Conditional CSS Class Composition in JavaScript Applications
clsxclasscatclassnamesSimilar Packages:

Conditional CSS Class Composition in JavaScript Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
clsx50,162,4669,7118.55 kB142 years agoMIT
classcat4,260,7249135.19 kB12 years agoMIT
classnames017,82923.6 kB82 years agoMIT

Conditional CSS Class Composition: classcat vs classnames vs clsx

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.

🧩 Core API Design: How You Pass Arguments

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: classcat does not flatten nested arrays beyond one level. Passing [['b']] results in unintended output.

⚡ Performance and Runtime Behavior

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.

📝 Developer Experience and Readability

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.

🧪 Edge Case Handling

All three ignore false, null, undefined, 0, and empty strings. But they diverge on edge inputs:

  • Numbers: classnames and clsx ignore 0 but include other numbers (e.g., 42 becomes '42'). classcat behaves the same.
  • Nested arrays: Only classnames fully flattens them. clsx flattens one level; classcat does not flatten nested arrays at all.
  • Objects with non-boolean values: All treat truthy values as “include” and falsy as “exclude.” So { 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]'

🛠️ When to Use Which?

Use clsx as your default

For 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.

Stick with classnames for legacy or maximum compatibility

If 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.

Choose classcat for extreme minimalism

If 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.

✅ Summary Table

Featureclasscatclassnamesclsx
Top-level argsSingle array onlyVariadicVariadic
Nested array supportNone (shallow)Full recursionOne level only
PerformanceFastestSlowestVery fast
Bundle impactSmallestLargestVery small
DX familiarityLowerHighestHigh
Drop-in replacement for classnames?NoN/AYes (in most cases)

💡 Final Thought

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.

How to Choose: clsx vs classcat vs classnames

  • clsx:

    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.

  • classcat:

    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.

  • classnames:

    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.

README for clsx

clsx CI codecov licenses

A tiny (239B) utility for constructing className strings conditionally.
Also serves as a faster & smaller drop-in replacement for the classnames module.

This module is available in three formats:

  • ES Module: dist/clsx.mjs
  • CommonJS: dist/clsx.js
  • UMD: dist/clsx.min.js

Install

$ npm install --save clsx

Usage

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'

API

clsx(...input)

Returns: String

input

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);
//=> ''

Modes

There are multiple "versions" of clsx available, which allows you to bring only the functionality you need!

clsx

Size (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/lite

Size (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 });
//=> ""

Benchmarks

For snapshots of cross-browser results, check out the bench directory~!

Support

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.x and beware of #17.

Tailwind Support

Here some additional (optional) steps to enable classes autocompletion using clsx with Tailwind CSS.

Visual Studio Code
  1. Install the "Tailwind CSS IntelliSense" Visual Studio Code extension

  2. 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);

Related

  • obj-str - A smaller (96B) and similiar utility that only works with Objects.

License

MIT © Luke Edwards