Theming
duck/ui implements the whole shadcn variable contract and adds five of its own. Components never reference a color directly, so retuning the tokens retunes everything.
Surfaces
Every value is OKLCH. Lightness is the first number, so a palette can be brightened or dimmed without hue drift, which is the whole reason the system uses it.
--backgroundPage canvas--foregroundBody text--cardRaised surfaces--popoverOverlays--mutedQuiet fills--borderHairlines and sticker bordersAccents
--primaryDuck lime, every default action--secondaryLow emphasis fills--accentCool highlight--destructiveDanger--ringFocus ringsDuck extras
Five variables shadcn does not define. Components rely on them, so a theme that omits them will render flat.
| Variable | Kind | Role |
|---|---|---|
| --holo | linear-gradient | The signature gradient. Used for borders and text. |
| --foil | conic-gradient | Full-spectrum foil for surfaces that track the pointer. |
| --glow | box-shadow | Soft cyan outer glow. |
| --glow-primary | box-shadow | Soft lime outer glow, used on primary hover. |
| --sticker-border | 3px | Border width for the sticker look. |
Utilities
Class names the theme installs. Components use them, and so can you.
.holo-borderIridescent border on any element..holo-border-animatedThe same border, drifting..holo-textIridescent text fill..foilPointer-reactive foil surface..sheenLight sweeping across on hover..tilt3D lean driven by --rx and --ry..duck-glow / .duck-glow-primarySoft outer glows..stickerBorder width from --sticker-border..kiss-cutSticker sheet backing paper.Retuning the palette
Change the hue in one place. Components have no opinion about which color primary is.
/* Every duck lime value comes from one hue. Move it and the
whole system follows, including glows and focus rings. */
.dark {
--primary: oklch(0.85 0.17 265); /* lime becomes violet */
--primary-foreground: oklch(0.24 0.07 265);
--ring: oklch(0.85 0.17 265);
--glow-primary: 0 0 32px oklch(0.85 0.17 265 / 0.35);
}Light and dark
Dark values live under .dark and light values under :root. The dark set is the reference: it was drawn first, and the light set was tuned against it rather than calculated from it.
Both sets are checked for WCAG AA contrast on body text and on every interactive state. If you retune, check both. A hue that works on near-black often fails on white.
