tailwind2-6 Exercise 1: Which Class Wins, and What Does twMerge Know? ==================================================================== Setup: a folder with npm install tailwindcss @tailwindcss/cli clsx tailwind-merge plus the files below. Build with npx @tailwindcss/cli -i s.css -o out.css Open page.html in Chrome. The numbers below came from a scripted headless Chrome (cdp.mjs, my own DevTools driver) that read the computed style of each element; you can read the same values in DevTools. --- s.css --- @import "tailwindcss"; @theme { --text-huge: 4rem; --color-brand: oklch(55% .2 260); } --- page.html ---
a
b
c
d
Order of the rules in out.css: .bg-blue-500, .bg-red-500, .px-2, .px-8 Result in Chrome: a class="bg-red-500 bg-blue-500" background oklch(0.637 0.237 25.331) padding-left 0px b class="bg-blue-500 bg-red-500" background oklch(0.637 0.237 25.331) padding-left 0px c class="px-8 px-2" padding-left 32px d class="px-2 px-8" padding-left 32px --- m.mjs (the merge table) --- import { clsx } from 'clsx'; import { twMerge, extendTailwindMerge } from 'tailwind-merge'; const show=(l,v)=>console.log(l.padEnd(46),JSON.stringify(v)); show('clsx("px-2", false, "px-8")', clsx("px-2", false && "x", "px-8")); show('twMerge("px-2 px-8")', twMerge("px-2 px-8")); show('twMerge("p-4 px-2")', twMerge("p-4 px-2")); show('twMerge("px-2 p-4")', twMerge("px-2 p-4")); show('twMerge("bg-red-500 bg-blue-500")', twMerge("bg-red-500 bg-blue-500")); show('twMerge("text-lg text-brand") (colour+size)', twMerge("text-lg text-brand")); show('twMerge("text-huge text-lg") custom size', twMerge("text-huge text-lg")); show('twMerge("text-brand text-red-500") custom colour', twMerge("text-brand text-red-500")); show('twMerge("hover:bg-red-500 bg-blue-500")', twMerge("hover:bg-red-500 bg-blue-500")); show('twMerge("rounded-md rounded-lg")', twMerge("rounded-md rounded-lg")); show('twMerge("w-4 size-8")', twMerge("w-4 size-8")); show('twMerge("!p-2 p-4") bang', twMerge("!p-2 p-4")); show('twMerge("p-2! p-4") v4 important', twMerge("p-2! p-4")); const tm = extendTailwindMerge({ extend: { classGroups: { 'font-size': [{ text: ['huge'] }] } } }); show('extended: text-huge text-lg', tm("text-huge text-lg")); show('extended: text-lg text-huge', tm("text-lg text-huge")); Output: clsx("px-2", false, "px-8") "px-2 px-8" twMerge("px-2 px-8") "px-8" twMerge("p-4 px-2") "p-4 px-2" twMerge("px-2 p-4") "p-4" twMerge("bg-red-500 bg-blue-500") "bg-blue-500" twMerge("text-lg text-brand") (colour+size) "text-lg text-brand" twMerge("text-huge text-lg") custom size "text-huge text-lg" twMerge("text-brand text-red-500") custom colour "text-red-500" twMerge("hover:bg-red-500 bg-blue-500") "hover:bg-red-500 bg-blue-500" twMerge("rounded-md rounded-lg") "rounded-lg" twMerge("w-4 size-8") "size-8" twMerge("!p-2 p-4") bang "!p-2 p-4" twMerge("p-2! p-4") v4 important "p-2! p-4" extended: text-huge text-lg "text-lg" extended: text-lg text-huge "text-huge" WHY THIS WORKS AS AN ANSWER --------------------------- Elements a and b have the same classes in opposite order and the same colour, and c and d the same padding, so the attribute order has no effect: both classes have equal specificity and the rule that Tailwind wrote later in out.css wins (red after blue, px-8 after px-2). A component cannot rely on "the caller's class comes last", so conflicting classes must be removed from the string. twMerge does that for classes it knows: later px-8 replaces px-2; p-4 replaces an earlier px-2 (it covers it) but px-2 after p-4 is kept because it is more specific; a hover: class does not conflict with a plain one because they are different variants. The custom size is the limit: twMerge has no access to your @theme, so "text-huge text-lg" kept both font sizes. extendTailwindMerge with a font-size group containing "huge" fixed it in both orders. The custom colour case "text-brand text-red-500" came out right only because an unknown name after text- is treated as a colour; do not rely on that for sizes, shadows or radii. The two "!important" lines kept both classes; I did not investigate that beyond noting it.