Open-source project
JedWatson/classnames avatar
JedWatson/classnames

classnames: Conditional Class Name Joining for React and the Browser

A simple javascript utility for conditionally joining classNames together

17,778 stars558 forksJavaScriptMIT

At a glance

What is it?
classnames is a tiny JavaScript utility that joins CSS class names together, with support for conditionally including or excluding names based on truthy values. It is the official successor to React's deprecated classSet helper and is designed for any environment that runs JavaScript.
Who is it for?
classnames suits any JavaScript project that builds class name strings conditionally. It is the standard choice in React codebases that need to combine static classes with state-driven ones.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The Problem classnames Solves

Building a className string in JavaScript with conditional logic produces verbose code. The README shows the common pattern: start with a base class string, then append class names with if statements based on component state. Each condition adds another string concatenation.

classnames replaces that pattern with a function that accepts strings, objects, and arrays in any combination, and returns a space-joined string of only the truthy entries. The README gives this summary of how arguments are handled:

- A string argument is always included. - An object argument includes only the keys whose values are truthy. - An array is recursively flattened by the same rules. - Falsy values (null, false, undefined, 0, empty string) are silently ignored.

This means the caller never writes an if statement for a class name. The condition lives inside the object literal passed to classNames().

Installing and Importing classnames

classnames is published to the npm registry. Install it with your package manager:

bash
npm install classnames

Import or require it in any module environment:

js
const classNames = require('classnames');
classNames('foo', 'bar'); // => 'foo bar'

The package.json sets type to module and exports separate paths for the main entry, the bind variant, and the dedupe variant. For standalone use without a bundler, include index.js in a script tag; the library exports a global classNames method or defines the AMD module if RequireJS is present.

The package's sideEffects field is false, which signals to bundlers that it can be tree-shaken. The three entry points (index.js, bind.js, dedupe.js) are each independent and are not imported by each other.

Core API: Strings, Objects, and Arrays

The README documents the full argument behavior with concrete examples:

js
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

Falsy arguments of any type are ignored:

js
classNames(null, false, 'bar', undefined, 0, { baz: null }, ''); // => 'bar'

Arrays are flattened recursively:

js
const arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'

With ES2015 computed keys, dynamic class names based on variables become straightforward:

js
const buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });

This is the pattern the README highlights for reducing boilerplate in components with multiple state variables.

Using classnames in a React Component

The README's primary motivating example is a React button component. Without classnames, the component builds a class string with three separate if/else blocks checking isPressed and isHovered state variables. With classnames, all three conditions collapse into one object literal:

js
import classNames from 'classnames';

const btnClass = classNames({
  btn: true,
  'btn-pressed': isPressed,
  'btn-over': !isPressed && isHovered,
});

Because classNames ignores falsy arguments, optional className props passed in from a parent component can be included without a separate null check:

js
const btnClass = classNames('btn', this.props.className, {
  'btn-pressed': isPressed,
  'btn-over': !isPressed && isHovered,
});

If this.props.className is undefined, classNames treats it as a falsy value and omits it. This is the practical reason classnames is described in the README as the official replacement for React's deprecated classSet helper.

The dedupe and bind Variants

classnames ships two alternate entry points for specific use cases.

The dedupe variant deduplicates class names and ensures that a falsy value for a class in a later argument overrides an earlier truthy value:

js
const classNames = require('classnames/dedupe');
classNames('foo', 'foo', 'bar'); // => 'foo bar'
classNames('foo', { foo: false, bar: true }); // => 'bar'

The README states dedupe is about 5x slower than the default and is opt-in for that reason. The default variant does not deduplicate; if you pass a class name twice, it appears twice in the output.

The bind variant is for CSS Modules, where class names are local identifiers that map to hashed strings in the DOM. After calling classNames.bind(styles), the bound function resolves local names through the styles object:

js
const classNames = require('classnames/bind');
const styles = { foo: 'abc', bar: 'def', baz: 'xyz' };
const cx = classNames.bind(styles);
cx('foo', ['bar'], { baz: true }); // => 'abc def xyz'

The README notes that in ES2015 environments, the dynamic class names approach with computed keys is often a cleaner alternative to bind.

Performance Philosophy and Polyfill Requirements

The README states that performance is taken seriously because the package runs millions of times per day in browsers. Updates are reviewed for performance implications before release, and a benchmarks/ directory in the repository contains comparison scripts. The README does not publish specific benchmark numbers; it describes the review process without committing to a throughput figure.

For environments older than Internet Explorer 9, the README notes that classnames 2.0.0 and above uses Array.isArray, which requires a polyfill on IE8 and below. A link to the MDN documentation for Array.isArray polyfills is provided. The README does not list any other browser compatibility constraints.

classnames follows the SemVer standard and ships a HISTORY.md changelog. The current package version is 2.5.1. The package.json dev scripts use node --test for the test runner, tsd for TypeScript type checking, and rollup for browser bundle builds. TypeScript definitions ship as index.d.ts, bind.d.ts, and dedupe.d.ts alongside each entry point.

classnames vs clsx, twmerge, and the cn Pattern

The main alternative to classnames in modern React codebases is clsx, which was written as a smaller and faster drop-in replacement. The classnames README does not reference clsx directly. The two packages have the same API surface for the most common cases (string, object, and array arguments), but clsx does not include a dedupe or bind variant.

For projects using Tailwind CSS, class name conflicts are a practical concern: two classes affecting the same CSS property both end up in the string, and the one that wins is determined by the order in the stylesheet, not by the order in the argument list. classnames does not resolve this. The tailwind-merge package (twmerge) addresses it by understanding which Tailwind classes conflict. A common pattern in Tailwind codebases is a cn() wrapper that calls clsx for conditional logic and twmerge for conflict resolution. classnames alone does not serve that role.

For CSS Modules users who want the bind behavior, classnames/bind is documented in the README. The ES2015 computed key approach is noted as a cleaner alternative in environments that support it. Neither clsx nor the manual concatenation approach offers a bind equivalent.

Editorial conclusion

classnames suits any JavaScript project that builds class name strings conditionally. It is the standard choice in React codebases that need to combine static classes with state-driven ones. It is not the right tool when you also need Tailwind CSS conflict resolution, where twmerge or clsx plus twmerge handles the merge-and-dedupe step that classnames does not. Before adopting it, check whether your bundler's tree-shaking handles the package.json exports correctly, since the module exposes three separate entry points.

Frequently asked questions

What are classNames in React?

In React, className is the prop used to set CSS classes on an element (equivalent to the class attribute in HTML). The classnames npm package is a utility for building the className string conditionally, combining static class names with ones that depend on component state or props.

How do I use classnames in React?

Install classnames with npm install classnames, import it, and call classNames() with a mix of strings and objects. Keys in an object argument are included in the output only when their value is truthy. Pass this.props.className directly as an argument and it is included or omitted depending on whether it is defined.

How do I add multiple classNames in React?

Pass multiple arguments to the classNames() function. Each string argument is always included, and each object key is included only when its value is truthy. Arrays are recursively flattened. The result is a single space-joined string suitable for the className prop.

What is the difference between classnames and clsx performance?

The classnames README does not include a direct benchmark against clsx. It does document that the dedupe variant of classnames is about 5x slower than the default variant. For common use cases without deduplication, the difference between classnames and clsx is not documented in the repository material.

Official sources

  1. Issues
  2. JedWatson/classnames on GitHub
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jedwatson-classnames.svg)](https://hysenlabs.com/projects/jedwatson-classnames)