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.
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.
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.
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
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.
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.
import { Button } from "@cooud-ui/ui";// Prefer semantic tokens over raw colors so overrides re-theme with the app.export function CalloutButton() {return (<Buttonvariant="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.
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 (<divclassName={cn("rounded-lg border border-border bg-surface-raised p-4", className)}{...props}/>);}
Keep overrides token-based
cn in your own components so a passed className always merges last instead of fighting your defaults.