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:
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 trueNow className, classNames, and compound variants can replace the
classes they conflict with.
Using cn
Section titled “Using cn”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:
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.
What it costs
Section titled “What it costs”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.