@batthewz/response-ui-tw-merge
v0.1.3
Published
tailwind-merge configured for the response-ui design system's custom utilities (r1..r6 spacing, semantic colors, h1..h6 and body-1..body-3 text scales). Pairs with @batthewz/response-ui-css.
Downloads
297
Maintainers
Readme
@batthewz/response-ui-tw-merge
tailwind-merge pre-configured for the response-ui design system's custom utilities, plus an extensible cn() className helper. Tiny, zero-runtime-dependencies-beyond-peers.
What it knows about
- Responsive spacing scale:
r1..r6(e.g.p-r3,gap-r4) - Semantic colors:
canvas,primary,surface-0..3,fg-primary,status-error, etc. - Heading + body text scales:
h1..h6,body-1..body-3
Without this configuration, tailwind-merge doesn't know that p-r3 and p-r5 conflict, or that text-fg-primary and text-fg-muted are alternatives — cn() would happily emit both and let the cascade decide.
Pairing with @batthewz/response-ui-css
This package is the JS-side companion to @batthewz/response-ui-css. It does not import the CSS package at runtime, but the token list it ships mirrors the tokens defined in response-ui-css. They are intentionally separate npm packages so that:
response-ui-cssstays pure CSS — pure-CSS consumers (Vue, Svelte, vanilla, plain HTML) pay zero JS cost.- This package can be used independently by anyone who wants
cn()with the same conventions in a non-React, non-CSS-consuming context.
Important: if you add a new token (color, spacing step, text scale) to response-ui-css, you must update the corresponding array in this package's src/index.js too, or tailwind-merge won't know about it. This is the trade-off for keeping the packages decoupled.
In practice you almost always want both installed together.
Install
npm install @batthewz/response-ui-tw-merge clsx tailwind-merge
# and the styles:
npm install @batthewz/response-ui-cssclsx and tailwind-merge are peer dependencies — bring your own to avoid duplicate copies in the bundle.
Usage
No custom tokens — just import cn
import { cn } from "@batthewz/response-ui-tw-merge";
function Button({ className, ...props }) {
return (
<button
className={cn(
"px-r4 py-r3 text-body-2 text-fg-on-primary bg-primary",
"hover:bg-primary-hover",
className,
)}
{...props}
/>
);
}cn() runs your inputs through clsx (conditional class merging) and then through a tailwind-merge instance that's been taught the design system's tokens, so the last-spec-wins rule works correctly for the custom utilities.
Your app adds custom tokens — use createCn
When your app extends the design system with its own colors, spacing, etc., create a project-local cn that knows about both the response-ui tokens and yours:
// app/cn.ts
import { createCn } from "@batthewz/response-ui-tw-merge";
export const cn = createCn({
theme: {
color: ["brand-primary", "brand-accent"],
spacing: ["xtra-tight", "xtra-wide"],
},
});Then import cn from app/cn everywhere instead of from the package directly.
Why a factory rather than spread? The factory concatenates your arrays onto the built-ins. With the older spread pattern, customising one theme key (e.g. color) while forgetting to spread the others (spacing, text) silently wiped the built-in awareness for those. With createCn that footgun is impossible — every built-in stays known no matter which keys you customise.
You can also pass through tailwind-merge's other config keys (classGroups, conflictingClassGroups, cacheSize, etc.):
export const cn = createCn({
theme: { color: ["brand-x"] },
classGroups: {
"custom-prose": ["prose-tight", "prose-loose"],
},
cacheSize: 1000,
});Low-level escape hatches
For power users who want to drive extendTailwindMerge themselves:
import { mergeExtension, tailwindMergeExtension } from "@batthewz/response-ui-tw-merge";
import { extendTailwindMerge } from "tailwind-merge";
// `mergeExtension` does the safe concat for you:
const config = mergeExtension({
theme: { color: ["brand-x"] },
});
const twMerge = extendTailwindMerge({ extend: config });
// Or use the raw built-in extension config (frozen, immutable):
const twMergeStrict = extendTailwindMerge({ extend: tailwindMergeExtension });A pre-configured twMerge (design-system tokens only, no extension) is also exported if you want twMerge semantics without clsx on top:
import { twMerge } from "@batthewz/response-ui-tw-merge";
twMerge("p-r3 p-r5"); // → "p-r5"Extending
Adding your own utilities and need cn() to merge them? See
docs/extending.md for the createCn / mergeExtension workflow and
how it pairs with generating the utility in your CSS layer.
License
MIT
