Skip to content

Documentation

Styling

Override, extend, and re-skin any component — className wins, variants and data-slots stay consistent, and tokens keep everything on-theme.

className always wins

Every component merges your className last through cn (clsx + tailwind-merge). You can override any utility without !important, and conflicting Tailwind classes are de-duplicated so the last one wins.

tsx
import { Button } from "@cooud-ui/ui";
export function CheckoutButton() {
// className is merged last, so these win without !important.
// Conflicting Tailwind utilities are de-duped — the last value wins.
return (
<Button className="w-full rounded-full">
Complete purchase
</Button>
);
}

Because tailwind-merge de-dupes conflicts, passing rounded-full cleanly replaces the component's default rounded-lg — no specificity tricks required.

Reuse variants with CVA

Components built on class-variance-authority export a *Variants function — for example buttonVariants. Call it to apply the same look to an element that isn't the component, so a styled link matches your buttons exactly.

tsx
import { buttonVariants } from "@cooud-ui/ui";
import Link from "next/link";
// Reuse a component's variants on an element that isn't that component.
export function DocsLink() {
return (
<Link href="/docs" className={buttonVariants({ variant: "outline", size: "sm" })}>
Read the docs
</Link>
);
}

buttonVariants accepts the same variant (primary, gradient, secondary, outline, ghost, destructive, link) and size (sm, md, lg, icon, icon-sm) options as Button itself.

Target internal parts with data-slot

Composed components tag their internals with a data-slot attribute. From a parent you can style any part with an arbitrary variant selector, so you don't have to thread className through every subcomponent.

tsx
import { Card, CardContent, CardHeader, CardTitle } from "@cooud-ui/ui";
// Reach into a composed component's internals from the parent
// by targeting its data-slot with an arbitrary variant.
export function HighlightCard() {
return (
<Card className="[&_[data-slot=card-title]]:text-primary">
<CardHeader>
<CardTitle>Pro plan</CardTitle>
</CardHeader>
<CardContent>Everything in Starter, plus priority support.</CardContent>
</Card>
);
}

Slots are the styling seams

Each subcomponent carries its own slot — data-slot="card-title", data-slot="card-header", data-slot="card-content". Target them with [&_[data-slot=…]]: to re-skin a part from the outside.

Compose with asChild

Pass asChild to render your own element while inheriting the component's behavior and styles. It uses Radix's Slot under the hood, so a Button asChild wrapping a Next <Link> stays a real anchor.

tsx
import { Button } from "@cooud-ui/ui";
import Link from "next/link";
// asChild renders YOUR element with the Button's behavior and styles —
// here a real <a> from Next's <Link>, not a <button>.
export function UpgradeCta() {
return (
<Button asChild variant="gradient" size="lg">
<Link href="/pricing">Upgrade now</Link>
</Button>
);
}

The wrapped element receives the component's classes and props, which keeps semantics correct — a link renders as an <a> instead of a <button>.

Prefer tokens over raw values

When you override styles, reach for semantic tokens instead of hard-coded colors. Tokens like bg-surface-raised, text-fg, border-border, and text-primary re-theme with the app, so your customizations follow the active theme and mode.

tsx
import { Button } from "@cooud-ui/ui";
// Prefer semantic tokens over raw colors so overrides re-theme with the app.
export function CalloutButton() {
return (
<Button
variant="outline"
className="bg-surface-raised text-fg border-border hover:text-primary"
>
Learn more
</Button>
);
}

The same tokens power every component, so token-based overrides stay correct across themes and presets. See Theming for how those tokens are defined and swapped.

Stay merge-safe in your own components

When you wrap or build on Cooud UI, run your classes through cn too. That keeps consumer className overrides predictable — the same last-wins behavior every Cooud UI component relies on.

tsx
import { cn } from "@cooud-ui/ui";
// Use cn in your own components so consumer className stays merge-safe.
export function Panel({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
className={cn("rounded-lg border border-border bg-surface-raised p-4", className)}
{...props}
/>
);
}

Keep overrides token-based

Favor semantic tokens over raw colors so customizations re-theme with the app, and reach for cn in your own components so a passed className always merges last instead of fighting your defaults.