Theming
ONONC components style themselves with a small, shared set of Tailwind v4 design tokens. Set them up once — with the CLI or by hand — and every component renders correctly, in both light and dark mode.
Install the theme
Here is the good news: you usually don't have to think about this at all. Installing any component with the shadcn CLI writes the tokens into your project automatically. But if you want the tokens on their own — for example before you copy-paste a component by hand, or to share one theme across a design system — install the standalone theme item once:
1npx shadcn@latest add https://dev.ononc.com/r/ononc-theme.json
This writes the @theme tokens, the :root/.dark variables, keyframes, and utilities into your globals.css — with no component files added.
Design tokens
Rather than hard-coding colors, ONONC components paint themselves with a small set of Tailwind v4 tokens. In your markup they show up as ordinary utility classes — bg-surface, text-foreground, border-border, bg-brand — so a component always adopts the theme it is dropped into. Here is the full set, with the value each takes in light and dark mode:
| Token | Purpose | Light | Dark |
|---|---|---|---|
background | Page background | #f6f7fb | #06070d |
surface | Cards & raised panels | #ffffff | #0b0d18 |
surface-2 | Secondary surface | #eef0f7 | #11142370 |
foreground | Primary text | #0c0e1a | #e9ebf5 |
muted | Secondary text | #545b70 | #8a91a8 |
muted-2 | Faint text / hints | #8a91a8 | #5b6178 |
border | Hairline borders | rgba(12,14,26,.1) | rgba(255,255,255,.08) |
border-strong | Stronger borders | rgba(12,14,26,.16) | rgba(255,255,255,.14) |
brand | Primary brand (violet) | #8b5cf6 | #8b5cf6 |
brand-ink | Brand text / ink | #6d28d9 | #c4b5fd |
brand-2 | Secondary brand (cyan) | #0891b2 | #22d3ee |
brand-3 | Accent (rose) | #e11d62 | #fb7185 |
ring | Focus-ring color | rgba(139,92,246,.55) | rgba(139,92,246,.55) |
Each token maps to a Tailwind color utility through @theme, so surface becomes bg-surface, text-surface, border-surface, and so on. The one exception in feel is ring: it is the color of the :focus-visible outline that every interactive component draws.
Light & dark mode
ONONC is dark-first, but every token is defined twice. :root holds the default (light) values, and .dark overrides the ones that change for dark mode. Dark mode itself switches on the moment the .dark class is present on the <html> element — toggle that class with your theme switcher of choice and every token flips at once. Here is the shape of it, abbreviated:
1@import "tailwindcss";23/* Dark mode turns on via a .dark class on <html> */4@custom-variant dark (&:where(.dark, .dark *));56:root {7 --background: #f6f7fb; /* light */8 --surface: #ffffff;9 --foreground: #0c0e1a;10 --brand: #8b5cf6;11}1213.dark {14 --background: #06070d; /* dark override */15 --surface: #0b0d18;16 --foreground: #e9ebf5;17 --brand-ink: #c4b5fd;18}1920@theme inline {21 /* expose the vars as Tailwind utilities: bg-surface, text-foreground, ... */22 --color-background: var(--background);23 --color-surface: var(--surface);24 --color-foreground: var(--foreground);25 --color-brand: var(--brand);26}
The cn helper
Nearly every ONONC component imports a tiny helper called cn to merge Tailwind class names, so a caller's classNamecleanly overrides the component's defaults instead of fighting them. It lives at src/lib/utils.ts and leans on two small packages, clsx and tailwind-merge. The CLI adds it for you; if you copy a component by hand, add this file too or its import will break:
1import { type ClassValue, clsx } from "clsx";2import { twMerge } from "tailwind-merge";34/** Merge Tailwind classes, resolving conflicts (clsx + tailwind-merge). */5export function cn(...inputs: ClassValue[]) {6 return twMerge(clsx(inputs));7}
Copying tokens by hand
Not using the CLI? No problem — do these three things once, and every component you copy afterward will render correctly:
- 1Copy the token layer — the
@themeblock, the:root/.darkvariables, the@keyframes, and the@utilityhelpers — fromsrc/app/globals.cssinto your ownglobals.css. - 2Add the
cnhelper shown above atsrc/lib/utils.ts. - 3Install the two small packages it depends on:1npm i clsx tailwind-merge
Want the complete, annotated list of tokens to copy? See the design tokens guide.
Customizing your theme
Because everything routes through CSS variables, restyling ONONC is mostly a matter of editing values — no component changes required. To recolor, change a token's value in both :root and .dark so it holds in either mode. Corner rounding lives in the --radius-4xl token (the rounded-4xl utility), and typography is driven by --font-sans and --font-mono. For example:
1.dark {2 --brand: #ec4899; /* make the brand pink */3 --radius-4xl: 1.5rem; /* tighter corners */4}
A note on token names
A few tokens use deliberately generic names — background, foreground, border, muted, ring. If you install ONONC into a project that already defines those — an existing shadcn theme, say — the incoming values will override yours. That is easy to live with: reconcile the overlapping tokens, or scope ONONC's under a wrapper class, so the two themes can coexist.