Customization

ThemingChangelog

Style any component or any part of it with className, classNames, CSS or Tailwind.

Every krz-ui component can be restyled from your side — the whole component, any part of it, or just its colors. Use whichever fits your stack: plain CSS, CSS modules, Tailwind, or inline styles.

The same props on every component

PropWhat it does
classNameAdded to the component's outer element. The built-in krz-* class stays, so both apply.
styleInline styles on the same outer element.
Native attributesid, data-*, aria-*, event handlers and the rest go to the element you'd expect (see below).
refForwarded to that same element.
classNamesAn object of extra classes for inner parts, e.g. { label, input, overlay, item }.
<Button className="w-full" style={{ letterSpacing: 1 }} data-testid="save" ref={buttonRef}>
  Save
</Button>

Changing colors with className

Give the component a class and set background and color — it wins in every state, including hover. Add your own :hover rule if you want a hover color.

<Button className="btn-success">Approve</Button>
styles.css
.btn-success {
  background: #16a34a;
  color: #fff;
}
.btn-success:hover {
  background: #15803d;
}

Use the background shorthand rather than background-color: some variants use a gradient, and the shorthand replaces it.

Where the props go

For most components everything lands on the outer element. A few components put native attributes somewhere more useful:

ComponentsBehavior
Form fields — Input, Textarea, Checkbox, NumberInput, TagInput, ComboboxclassName and style style the whole field. Native input attributes (value, onChange, placeholder, name…) and ref go to the <input>, so form libraries work.
Select, DatePickerclassName and style style the whole field. Native attributes and ref go to the trigger button.
Modal, Drawer, AlertDialogclassName, style, native attributes and ref go to the dialog panel. Use classNames.overlay for the backdrop and classNames.root for the full-screen wrapper.
Tooltip, Popover, HoverCard, DropdownMenu, ContextMenuclassName goes on the wrapper around the trigger. Style the floating part with classNames.content (or classNames.menu for menus).
ToastProviderclassName and style style the toast container. classNames styles every toast; toast({ className }) styles one.

Styling inner parts with classNames

Pass classNames to reach any part of a component — including parts rendered in a portal, like a modal's backdrop. Each component's part names are listed in its API Reference.

<Input
  label="Email"
  className="max-w-sm"
  classNames={{ label: "uppercase text-xs", control: "rounded-full", input: "font-mono" }}
/>

<Modal
  open={open}
  onClose={close}
  title="Invite"
  classNames={{ overlay: "backdrop-blur-md", footer: "justify-between" }}
/>

<Table
  data={invoices}
  rowKey={(r) => r.id}
  columns={columns}
  rowClassName={(r) => (r.overdue ? "bg-red-50" : undefined)}
/>

Targeting parts from CSS

Every part also has a stable krz-* class, and interactive states are exposed as data-* attributes, so you can style them from any stylesheet:

styles.css
/* Every primary button */
.krz-btn--primary { border-radius: 999px; }

/* Only inside your settings page */
.settings .krz-card { box-shadow: none; }

/* States */
.krz-accordion__item[data-state="open"] { background: #fafafa; }
.krz-tabs__tab[data-state="active"] { font-weight: 700; }
.krz-field[data-invalid] .krz-field__label { color: crimson; }

To change colors, radius or fonts everywhere at once, override the --krz-* CSS variables — see Theming.

Using Tailwind CSS

Tailwind v4 puts its utilities in a CSS cascade layer, and layered styles always lose to unlayered ones. Import the layered stylesheet and declare the layer order once, so your utility classes always win over krz-ui's defaults:

globals.css
@layer theme, base, krz-ui, components, utilities;
@import "tailwindcss";
@import "krz-ui/styles.layer.css";
<Button className="rounded-none px-8">Tailwind wins</Button>

Gradient backgrounds: primary buttons, primary badges, switches and progress bars use a gradient, which is a background-image. Tailwind's bg-* color utilities only set background-color, so add bg-none to remove the gradient first:

<Button className="bg-none bg-emerald-600 hover:bg-emerald-700">Solid green</Button>

Or keep a gradient and change its colors for just that element:

<Button className="[--krz-gradient:linear-gradient(135deg,#059669,#10b981)]">Green gradient</Button>

(To recolor every component at once, set --krz-primary and --krz-primary-2 on :root — see Theming.)

Plain CSS without specificity fights

With the default krz-ui/styles.css, a single-class rule of yours may lose to a more specific krz-ui rule. If you'd rather have any of your rules win, import krz-ui/styles.layer.css instead — every unlayered rule you write then overrides krz-ui automatically.

import "krz-ui/styles.layer.css";

Note that this also applies to broad page styles (e.g. button { background: none }), so keep global resets in their own layer if you use them.

Making your own variants

Because every component forwards its ref and props, wrapping one keeps everything working:

components/brand-button.tsx
import { forwardRef } from "react";
import { Button, cx, type ButtonProps } from "krz-ui";

export const BrandButton = forwardRef<HTMLButtonElement, ButtonProps>(function BrandButton(
  { className, ...props },
  ref,
) {
  return <Button ref={ref} className={cx("brand-button", className)} {...props} />;
});