4 min read
I bundled the two className helpers on this box: tailwind-merge 3.7.0 came out at 8,561 bytes gzip, while clsx 2.1.1 came out at 264 bytes. On 200k mixed-arg builds, clsx ran about 18.6M–20.0M ops/s versus about 4.4M–4.5M for twMerge, and only twMerge dropped conflicting Tailwind utilities.
Short answer: keep clsx (or clsx + a merge layer) when you only need conditional class strings. Reach for tailwind-merge when conflicting utilities like px-2 vs px-4 must resolve to one winner in the final class list. No affiliate links in this post.
| Build (esbuild browser minify) | Minified | gzip -9 |
|---|---|---|
| tailwind-merge | 27,407 B | 8,561 B |
| clsx | 412 B | 264 B |
About 32× smaller gzip for clsx. Pair this with the older class helper lab clsx vs classnames. Track client cost the same way as size-limit vs bundlesize. Tailwind-adjacent CSS tooling sits next to UnoCSS vs Tailwind and Lightning CSS vs PostCSS.
| 200k mixed builds (median of 9, 2 order-reversed runs) | tailwind-merge | clsx |
|---|---|---|
| Order twMerge then clsx (ms / ops/s, file 1) | 44.881 / ~4.46M | 10.740 / ~18.6M |
| Order clsx then twMerge (ms / ops/s, file 1) | 45.925 / ~4.35M | 10.635 / ~18.8M |
| File 2 repeats | 45.011 / 45.379 | 10.688 / 9.997 |
clsx stayed about 4× cheaper per call. That gap only matters in hot class rebuild loops; for typical React renders the conflict-resolution behavior usually wins the decision. Tiny string helpers in a nearby lane show up in picocolors vs chalk.
| Topic | tailwind-merge 3.7.0 | clsx 2.1.1 |
|---|---|---|
| Conditional false/null | drops them | drops them |
| "px-2 px-4" | "px-4" | "px-2 px-4" |
| Multi-arg conflict (bg-red + bg-blue) | last wins (bg-blue-500) | keeps both |
| Join without conflict rules | twJoin keeps both | n/a (clsx is join) |
They are not substitutes. clsx builds the string; twMerge also understands Tailwind conflict groups. Lint/format stacks that often ship with the same frontend decision sit next to Biome vs ESLint + Prettier. Asset pipelines nearby: SVGO vs SVGR.
| Situation | My pick |
|---|---|
| Conditional classes only, no Tailwind conflicts | clsx |
| Smallest browser helper on this box | clsx |
| Tailwind utilities that override each other | tailwind-merge |
| Already on clsx, need conflict drops | clsx + twMerge together |
| Image/asset minify instead of class strings | see sharp vs jimp |
Bottom line: tailwind-merge 3.7.0 was 8,561 B gzip versus 264 B for clsx 2.1.1, clsx ran about 4× more ops/s, and only twMerge turned "px-2 px-4" into "px-4". Who should not drop twMerge: any Tailwind UI that relies on later classes winning conflicts. Who should not add it: projects that never emit conflicting utilities and care about every kilobyte.
How this was made: I installed both packages on the ShopperCove box, measured esbuild browser bundles, timed 200k class builds in two order-reversed runs, and compared conflict/conditional/twJoin behavior. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~02:22 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). tailwind-merge 3.7.0 vs clsx 2.1.1, esbuild 0.28.2 browser ESM minify + gzip -9. Size: 27,407/8,561 B vs 412/264 B. Speed 200k mixed builds x 9-round median, order-reversed: twMerge ~44.9-45.9 ms (~4.4M ops/s) vs clsx ~10.0-10.7 ms (~18.6-20.0M). Conflict: twMerge px-2 px-4 -> px-4; clsx keeps both. Disk ~1.0 MB vs ~8 KB. Not tested: createTailwindMerge, RTL, Tailwind v4 custom tables. No affiliate.