tailwind-merge
Merge Tailwind CSS classes without style conflicts
Repository Health
Technical Analysis
tailwind-merge is a TypeScript utility that merges strings of Tailwind CSS classes and resolves conflicts between them, so that when two classes target the same styling concern the last one specified wins instead of both being applied and silently fighting in the CSS cascade.
It ships with zero runtime dependencies, understands the full default Tailwind config out of the box, and can be extended to understand custom themes, plugins, and class groups via extendTailwindMerge and mergeConfigs.
What You Get
- A single
twMerge()function that merges any number of class strings/arrays and strips out conflicting Tailwind utilities. - A
twJoin()helper for conditionally joining class names (a typed subset ofclsx) when conflict resolution isn’t needed. extendTailwindMerge,createTailwindMerge, andmergeConfigsfor teaching the merge logic about a customized Tailwind theme or plugin classes.- Full TypeScript types for every export, plus a legacy
./es5bundle for older browser targets.
Common Use Cases
- Accepting an overridable
classNameprop in a reusable component. - Merging variant classes generated by
cvaortailwind-variantsin a design system. - Resolving conditionally-applied Tailwind classes in state-driven UI.
- Supporting a customized Tailwind theme with non-default spacing/color scales.
Under The Hood
Architecture — twMerge (src/lib/tw-merge.ts) is createTailwindMerge(getDefaultConfig): a factory in src/lib/create-tailwind-merge.ts that lazily builds its config and an LRU cache (src/lib/lru-cache.ts) on first call, then swaps its internal functionToCall reference to a fast cached path for every call after — avoiding config-parsing cost when twMerge is never invoked. The actual merge happens in mergeClassList (src/lib/merge-classlist.ts): the input is first joined by twJoin (a typed clsx subset, src/lib/tw-join.ts), then class names are walked right-to-left, each parsed by parseClassName (src/lib/parse-class-name.ts) into modifiers, base class, important-flag, and postfix-modifier position while tracking bracket/paren depth for arbitrary values like bg-[#B91C1C]. Each class resolves to a classGroupId via config-utils’ class-group matching (built from src/lib/default-config.ts and src/lib/validators.ts); a class is dropped if a later class already claimed the same modifier+classGroupId conflict key, with getConflictingClassGroupIds expanding transitive conflicts (e.g. px conflicts with pl and pr).
Tech Stack — Pure TypeScript with zero runtime dependencies ("dependencies": {} in package.json); built with Rollup (scripts/rollup.config.mjs) into separate ESM/CJS/ES5 bundles exposed via package.json’s exports map (. vs ./es5); tested with Vitest 4 plus @vitest/coverage-v8 for coverage and @codspeed/vitest-plugin for benchmark-regression tracking; linted with ESLint 9’s flat config and typescript-eslint; managed as a pnpm 11 workspace (pnpm-workspace.yaml) requiring Node >=22 via devEngines.runtime.
Code Quality — 36 test files under tests/ cover arbitrary values/properties/variants, class-group conflicts, the important modifier, negative values, lazy initialization, and even a docs-examples.test.ts that runs the README’s own code samples to keep documentation honest. A dedicated typescript-compat devDependency pins an older TypeScript version to verify the shipped .d.ts declarations stay compatible. Source modules are small and single-purpose (parse-class-name.ts, merge-classlist.ts, lru-cache.ts, sort-modifiers.ts are each well under 150 lines) with inline comments justifying non-obvious performance choices, such as the “Fast path: skip sorting for empty or single modifier” branch in merge-classlist.ts.
API Design — The zero-config twMerge default covers the common case with no setup (twMerge('a', 'b')), while power users extend behavior via extendTailwindMerge, createTailwindMerge, and mergeConfigs without forking internals, and fromTheme lets custom class groups key off the consumer’s own Tailwind theme. Bundle size is treated as a first-class concern (a Bundlephobia badge in the README, a dedicated ./es5 export for legacy targets), and documentation is split into task-oriented pages (what-is-it-for, when-and-how-to-use-it, configuration, recipes, api-reference, writing-plugins) rather than one long README.
Used by 219 apps in this directory
Scalar
Developer Tools
Beautiful, interactive OpenAPI documentation with a built-in offline-first API client and multi-language code generation — all in one open-source platform.
Second Me
AI Assistants · Productivity
Train a locally hosted AI twin on your own memories—then connect it to the world through a decentralized identity network.
shaper
Analytics · Data Engineering
Build analytics dashboards, reports, and customer-facing analytics by writing pure SQL — powered by DuckDB and designed for self-hosted deployment.
sigle
Blogging
A decentralized, open-source writing platform that permanently stores your stories on the Stacks blockchain and Arweave — where Web3 content creators own their words forever.
Sink
Analytics · Bookmarks Archiving
Serverless link shortener with real-time analytics, powered by Cloudflare
sirchmunk
AI Development · Data Engineering
Drop your files and search them instantly — no vector DB, no indexing pipeline, just raw data queried by a self-evolving intelligence layer.
Skyvern
AI Agents · Automation
Skyvern (YC S2023) automates browser-based workflows by pairing LLMs with computer vision, letting agents click, fill, and extract data on sites they've never seen, without brittle XPath selectors that break on every layout change.
Snapify
Collaboration · Productivity
Open-source, self-hostable screen recording and video sharing built as a Loom alternative — no accounts required to watch, full S3-backed storage for your data.
solidtime
Invoicing Finance · Productivity
Modern open-source time tracker for freelancers and agencies with invoicing, multi-org support, and Toggl/Clockify migration built in.