cn — class-name merge utility
interlace-ui/cnFreeUtility
@interlace/ui — the cn() helper, alias-compatible with shadcn. Merges Tailwind class lists deterministically.
Install it
npx shadcn@latest add https://ds.interlace.tools/r/cn.jsonSource
1import { clsx, type ClassValue } from 'clsx';23// @interlace/cn v1.1.0 — Interlace design system.4// Docs, props and live preview: https://ds.interlace.tools/c/cn5// What changed since: https://ds.interlace.tools/c/cn#history6// Generated banner — keep it, the upgrade diff reads this version.78import { extendTailwindMerge } from 'tailwind-merge';910/**11 * The DS type scale, as declared in `styles/foundation.css`.12 *13 * Tailwind v4 generates a `text-<name>` utility for every `--text-<name>` theme14 * key, which collides head-on with `text-<color>`. tailwind-merge cannot tell15 * them apart from the class name alone — `text-ui` looks exactly like a colour16 * utility — so **with no configuration it files our size tokens under17 * `text-color` and deletes them whenever a real colour utility is also18 * present.**19 *20 * That is not theoretical. `twMerge('text-ui-sm font-medium text-foreground')`21 * returns `'font-medium text-foreground'`: the size is gone. It was shipping in22 * the rendered DOM in at least eight places — ProgressLabel, ProgressValue,23 * ToastTitle, ToastDescription, the CodeBlock header, its copy button, its24 * `<pre>` (which lost `text-code`), and `<Typography variant="ui" tone="muted">`,25 * which lost the size that is the entire point of the variant.26 *27 * Keep this list in sync with `--text-*` in `foundation.css`. A token added28 * there and forgotten here does not error — it silently loses its size at the29 * first `cn()` that also sets a colour, which is the worst possible failure30 * mode, so `__tests__/cn-type-scale-lock.test.ts` asserts the two agree.31 */32export const DS_FONT_SIZES = [33 'h1',34 'h2',35 'h3',36 'h4',37 'h5',38 'h6',39 'body',40 'long',41 'ui',42 'ui-sm',43 'caption',44 'code',45] as const;4647const twMerge = extendTailwindMerge({48 extend: {49 classGroups: {50 // Registering these as font-size means tailwind-merge treats them as51 // mutually exclusive with each other (`text-ui text-body` → `text-body`,52 // correct) and orthogonal to colour (`text-ui text-foreground` keeps53 // both, which is the bug this fixes).54 'font-size': DS_FONT_SIZES.map((size) => `text-${size}`),55 },56 },57});5859export function cn(...inputs: ClassValue[]) {60 return twMerge(clsx(inputs));61}
More from interlace-ui
All 140 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| No-flash theme bootstrap scripttheme-script | interlace-ui | Utilities | Free | no deps | |
| Theme token manifesttheme-tokens | interlace-ui | Utilities | Free | no deps | |
| useReducedMotion hookuse-reduced-motion | interlace-ui | Utilities | Free | no deps | |
| useTheme hookuse-theme | interlace-ui | Utilities | Free | no deps |
