1. Get components for Tailwind
Agents pass tailwind: true to get_component (over REST, ?tailwind=true). Each CSS module then comes inside Tailwind's components layer: a utility class you pass in className overrides the component, and Tailwind's preflight reset stays beneath it. Without the option the CSS is unlayered, which is right for a project without Tailwind.
2. Add the tokens and the bridge
Add tokens.css once, and tailwind.css (below) after it, in the stylesheet that imports Tailwind:
@import "tailwindcss";@import "./ui/tokens.css";@import "./ui/tailwind.css";The bridge names UI Polish's roles as theme values, so your own markup uses the same colors as the components: bg-ui-surface, text-ui-muted, border-ui-border, rounded-ui-md, shadow-ui-floating, ease-ui-enter. They're prefixed with ui- so they never collide with a theme you already have.
3. Point the roles at your theme
The components read --ui-* roles only. Set them from your palette, after the tokens, and the components take your brand:
:root { --ui-accent: light-dark(var(--color-indigo-600), var(--color-indigo-400)); --ui-accent-text: light-dark(var(--color-indigo-700), var(--color-indigo-300)); --ui-font-sans: var(--font-sans); --ui-radius-md: var(--radius-lg);}Colors take light-dark(), so each role keeps a value for each theme.
Overriding a component
- Layout and shape: margin, width, padding, radius, position. Pass the utility:
<Button className="w-full rounded-md">. - Color: components paint their fills on inner layers, from their own variables, so a press can shrink the fill without moving the label. Set the variables rather than the properties, the label's with the fill's:
<Button className="[--button-bg:var(--color-emerald-600)] [--button-fg:white]">. Hover and press shades follow from those two. Each component's variables are at the top of its CSS module. To change a color everywhere, set the role (--ui-accent) instead.
Dark mode
The roles follow color-scheme, as Tailwind's default dark: variant follows the system, so the two agree with nothing to set up. If you switch themes yourself, drive both from one attribute on <html>:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));Then set data-theme="dark" or data-theme="light" on <html> instead of a dark class: dark: utilities and the components switch together.
Tailwind v3
Tailwind v3 doesn't output cascade layers, so leave tailwind off: the CSS modules work as they are, and a utility that has to beat a component's own style needs the important modifier (!px-6).
tailwind.css
/* * UI Polish for Tailwind CSS v4. Import it after Tailwind and the tokens: * * @import "tailwindcss"; * @import "./ui/tokens.css"; * @import "./ui/tailwind.css"; * * Exposes the roles as theme values (bg-ui-surface, rounded-ui-md, ease-ui-enter), * prefixed ui- so they never collide with a project's own names like muted or border. */@theme inline { --color-ui-canvas: var(--ui-canvas); --color-ui-surface: var(--ui-surface); --color-ui-fill: var(--ui-fill); --color-ui-fill-strong: var(--ui-fill-strong); --color-ui-fill-solid: var(--ui-fill-solid); --color-ui-fill-strong-solid: var(--ui-fill-strong-solid); --color-ui-foreground: var(--ui-foreground); --color-ui-muted: var(--ui-muted); --color-ui-subtle: var(--ui-subtle); --color-ui-border: var(--ui-border); --color-ui-border-strong: var(--ui-border-strong); --color-ui-ink: var(--ui-ink); --color-ui-on-ink: var(--ui-on-ink); --color-ui-on-ink-muted: var(--ui-on-ink-muted); --color-ui-accent: var(--ui-accent); --color-ui-accent-text: var(--ui-accent-text); --color-ui-on-accent: var(--ui-on-accent); --color-ui-danger: var(--ui-danger); --color-ui-danger-text: var(--ui-danger-text); --color-ui-on-danger: var(--ui-on-danger); --color-ui-success: var(--ui-success); --color-ui-success-text: var(--ui-success-text); --color-ui-on-success: var(--ui-on-success); --color-ui-warning: var(--ui-warning); --color-ui-warning-text: var(--ui-warning-text); --color-ui-accent-tint: var(--ui-accent-tint); --color-ui-success-tint: var(--ui-success-tint); --color-ui-warning-tint: var(--ui-warning-tint); --color-ui-danger-tint: var(--ui-danger-tint); --spacing-ui-icon-xs: var(--ui-icon-xs); --spacing-ui-icon-sm: var(--ui-icon-sm); --spacing-ui-icon-md: var(--ui-icon-md); --spacing-ui-icon-lg: var(--ui-icon-lg); --radius-ui-sm: var(--ui-radius-sm); --radius-ui-md: var(--ui-radius-md); --radius-ui-lg: var(--ui-radius-lg); --shadow-ui-resting: var(--ui-shadow-resting); --shadow-ui-raised: var(--ui-shadow-raised); --shadow-ui-floating: var(--ui-shadow-floating); --ease-ui-enter: var(--ui-ease-enter); --ease-ui-exit: var(--ui-ease-exit); --ease-ui-move: var(--ui-ease-move); --ease-ui-glide: var(--ui-ease-glide); --ease-ui-snap: var(--ui-ease-snap); --ease-ui-color: var(--ui-ease-color);}