classnames
A tiny utility for conditionally joining CSS class names together.
Repository Health
Technical Analysis
classnames is a lightweight JavaScript utility, ubiquitous in the React ecosystem, that merges strings, objects, and arrays into a single space-separated className string. Instead of hand-building conditional className logic with ternaries and string concatenation, developers pass truthy/falsy flags as a plain object and let classnames decide which class names survive.
With zero runtime dependencies and a footprint of well under 1KB, it has become a de facto standard for building dynamic UI class lists in component-based frameworks, and ships bind and dedupe variants for CSS Modules and dedup-sensitive use cases respectively.
What You Get
- Zero-dependency, sub-1KB core
classNames()function for conditional class joining classnames/bindvariant pre-bound to a CSS Modules style map via acx()helperclassnames/dedupevariant that removes duplicate and overridden class names- Full TypeScript type definitions shipped inline (
index.d.ts,bind.d.ts,dedupe.d.ts) - ESM-only package with
sideEffects: falsefor optimal tree-shaking
Common Use Cases
- Toggling a CSS class based on component state (e.g.
isActive,isPressed,isHovered) - Merging a consumer-supplied
classNameprop with a component’s own base classes - Building dynamic utility-class strings from multiple conditional sources
- Binding to CSS Modules style objects to reference local class names instead of raw strings
Under The Hood
Architecture classnames is deliberately minimal: three independent entry points (index.js, bind.js, dedupe.js) share the same conceptual pipeline of recursively flattening arguments (string, object, or array) into class names, but each swaps its accumulation strategy for a different goal. The default build concatenates into a string as it goes; bind.js layers a this-bound lookup so cx() can translate logical names into CSS Modules’ hashed class names; dedupe.js instead accumulates into a null-prototype StorageObject keyed by class name so later truthy/falsy values naturally overwrite earlier ones, producing de-duplicated output at roughly 5x the cost of the default. Module boundaries are exposed cleanly via package.json exports subpaths rather than a single monolithic API.
Tech Stack The package itself has zero runtime dependencies and ships as pure ESM ("type": "module") with sideEffects: false for tree-shaking. Its devDependency set is entirely tooling: Rollup for browser benchmark bundling, tsd for asserting the shipped .d.ts files against real usage, and tinybench/http-server for the in-repo benchmark suite that runs in CI. TypeScript types are hand-written and versioned alongside the JS, not generated.
Code Quality The tests/ directory covers all three entry points (index.js, bind.js, dedupe.js) plus .test-d.ts type-assertion files, using Node’s built-in node:test runner — no external test framework dependency. Coverage is thorough for a small library: truthy/falsy edge cases (NaN, -0, empty string, empty object/array), deep array recursion, custom/inherited toString() overrides, and even execution inside a Node vm context to catch cross-realm bugs. CI (.github/workflows/node.js.yml) runs the test suite across Node 18/20/21, type-checks the .d.ts files, and runs the benchmark suite on every push and PR — there is no separate ESLint/Prettier config in the repo, so style consistency relies on convention rather than tooling.
API Design The public API is a single variadic function call that accepts strings, objects, arrays, or any mix thereof, which removes essentially all boilerplate compared to manual ternary/string-concatenation class building. Argument handling is intuitive and well documented in the README with realistic React examples. The bind and dedupe variants reuse the exact same calling convention as the default export, so switching between them is a one-line import change with no API relearning. The main friction point is the ESM-only, type: module packaging, which is a minor breaking change for consumers still on CommonJS-only tooling.
Used by 68 apps in this directory
Focalboard
Collaboration · Productivity · Project Management
Self-hosted, open source project management with Kanban, table, gallery, and calendar views — a privacy-first alternative to Trello, Notion, and Asana.
Forem
Blogging · Community
Open source Ruby on Rails platform for building developer communities with articles, discussions, and social profiles—the same software that powers dev.to.
GDevelop
Design Tools · Developer Tools · Game Development
No-code, open-source game engine for building 2D, 3D and multiplayer games — publish to iOS, Android, Steam and the web.
GrowthBook
Ab Testing Experimentation · Analytics · Developer Tools
Open source feature flags, A/B testing, and warehouse-native experimentation that queries your existing data infrastructure—no data movement required.
Hanko
Authentication · Security
Open source, self-hostable authentication platform with passkeys, SAML SSO, and OAuth — the privacy-first alternative to Auth0 and Clerk.
Harness Open Source
Code Editors · Developer Tools · Devops
A unified open source DevOps platform combining Git hosting, CI/CD pipelines, cloud development environments, and artifact registries in a single self-hosted system.
Hatchet
AI Development · Automation · Developer Tools
A Postgres-backed orchestration engine for background tasks, AI agents, and durable workflows that replaces Redis queues and multi-datastore durable execution platforms with a single self-hostable service.
Hi.Events
Ecommerce · Scheduling
Self-hosted event ticketing platform that keeps your attendee data, brand, and revenue completely under your control.
highlight.io
Analytics · Developer Tools · Monitoring
Open-source full-stack monitoring that unifies session replay, error tracking, logging, and distributed tracing so you can stop context-switching between tools.