Skip to content

Resolving conflicts with tailwind-merge

With the default join, every class is kept: when two classes set the same CSS property, such as px-4 and px-2, the one defined later in the stylesheet wins, whatever their order in the class name. You can avoid such conflicts by design (see Writing conflict-free recipes), or resolve them with a join function such as twMerge, which keeps the last class of each property.

Create the functions with it once, in a module of your own, with createRecipes:

src/lib/recipe.ts
import { createRecipes } from "@lynstack/class-recipe";
import { twMerge } from "tailwind-merge";
export const { cx, cva, sva } = createRecipes({ join: twMerge });
import { cva, cx } from "./lib/recipe";
const button = cva({
base: "rounded-md px-4 py-2",
variants: { size: { sm: "px-2 py-1", md: "" } },
});
button({ size: "sm", className: "px-3" }); // => "rounded-md py-1 px-3"
cx("p-2", isLarge && "p-4"); // => "p-4" when isLarge is true

Now className, classNames, and compound variants can replace the classes they conflict with.

cn, from the authors of shadcn/ui, replaces clsx and tailwind-merge with a faster engine that merges classes the same way. Pass cn itself as the join:

src/lib/recipe.ts
import { createRecipes } from "@lynstack/class-recipe";
import { cn } from "cn";
export const { cx, cva, sva } = createRecipes({ join: cn });

The cx it returns replaces cn itself: it joins its inputs as clsx does, then merges them. cn supports Tailwind CSS v4 only; on Tailwind CSS v3, use tailwind-merge v2. For a custom theme, pass the function that createCn from cn/config returns.

twMerge is slow next to concatenating strings, but a recipe calls it once for each declared selection and caches the result. Only a call that passes className or classNames, or an undeclared option, calls it again (see How it works). The configured cx calls it on every call, after joining its inputs like the default cx.

Since a recipe already caches its class names, a faster join, such as cn, mostly speeds up the configured cx and the calls that pass className or classNames.