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
Open Scouts
AI Agents · Automation
Create automated AI scouts that continuously search the web and send email alerts when they find what you're looking for.
OpenStatus
Devops · Monitoring
Open-source status pages and uptime monitoring with global synthetic checks, monitoring-as-code, and AI agent integration via MCP.
OpenCut
Design Tools · Social Media · Video Editors
Free, open-source video editor for web, desktop, and mobile — no watermarks, no subscriptions, built with a Rust core and plugin-first architecture
OpenHands
AI Code Assistants · AI Development
The self-hosted developer control center for running AI coding agents — locally, in Docker, on VMs, or across cloud backends — with automation workflows for GitHub, Slack, and more.
OpenPanel
Analytics
Open-source Mixpanel alternative with session replay, MCP integration, and privacy-first product analytics you fully control.
OpenReel Video
Video Editors
A browser-based, GPU-accelerated video editor with multi-track timelines, color grading, and 4K export — no uploads, no installs, no watermarks.
OpenScreen
Design Tools · Note Taking · Productivity
Free, open-source screen recorder with zooms, annotations, and system audio capture
Openship
Devops · Hosting Control Panel
Openship is an open-source, self-hostable deployment platform that points at a repo and builds, ships, routes, and TLS-terminates the app — driven from a desktop app, web dashboard, or CLI.
OpenStock
Analytics
Free, open-source stock market tracker with real-time data, watchlists, price alerts, and AI-powered market summaries.