Customize

Theming

Every color, radius, shadow and curve is a CSS variable in one file. Change a token and every component follows, in light and dark.

How tokens work

templates/vibeui.css declares each token twice: once for light (on :root and .light) and once for dark (on .dark). A @theme inline block then registers them with Tailwind, so they become normal utilities: bg-surface, text-muted, border-line, ring-ring, shadow-lift.

css
:root,
.light { --surface: #ffffff; }
.dark  { --surface: #181715; }

@theme inline { --color-surface: var(--surface); }
/* → bg-surface, text-surface, border-surface, … */

Because the utilities resolve to var(--surface) at runtime, you can override a token on any element and its subtree changes. That is how the landing page's playground retints live components, and how you can scope a dark panel inside a light page.

Color tokens

These are read straight from the token file, so the table is always current.

TokenLightDark
--canvas#f8f7f4#0f0e0d
--surface#ffffff#181715
--surface-muted#f2f0ec#201e1c
--surface-sunken#eceae4#121110
--ink#1b1a17#f4f2ee
--ink-soft#44423d#d6d3cc
--muted#77746d#9a968d
--faint#a6a39b#67645d
--linergb(27 26 23 / 0.09)rgb(244 242 238 / 0.09)
--line-strongrgb(27 26 23 / 0.17)rgb(244 242 238 / 0.18)
--overlayrgb(27 26 23 / 0.42)rgb(0 0 0 / 0.6)
--accent#1b1a17#f4f2ee
--accent-ink#ffffff#14130f
--accent-softrgb(27 26 23 / 0.08)rgb(244 242 238 / 0.1)
--ringrgb(27 26 23 / 0.22)rgb(244 242 238 / 0.26)
--pastel-rose#fdecec#3a2323
--pastel-rose-ink#a02e2c#f2b5b3
--pastel-sky#e7f1fb#1c2e3b
--pastel-sky-ink#1c5f93#9ecbea
--pastel-sage#e9f3ea#223127
--pastel-sage-ink#2c6a33#a9ceae
--pastel-sand#f0eeea#362f1d
--pastel-sand-ink#1b1a17#e8c978

Reading the palette

RoleTokensUsed for
Surfacescanvas, surface, surface-muted, surface-sunkenPage, cards and inputs, hovers and wells, tracks and code.
Textink, ink-soft, muted, faintPrimary, secondary, supporting, placeholder.
Linesline, line-strongHairlines and stronger borders.
Accentaccent, accent-ink, accent-soft, ringFocus, selection, progress, live state.
Statuspastel-{rose,sky,sage,sand} and -inkAlerts, pills, status. Retinted for dark.

Retint the accent

Both themes are monochrome by default: light mode's accent is the ink itself, dark mode's accent is the light ink. Override both after the token import. Keep --accent-ink readable on top of --accent: it is the text color for accent buttons, checked boxes and selected days.

globals.css
:root {
  --accent: #2563eb;
  --accent-ink: #ffffff;
  --accent-soft: rgb(37 99 235 / 0.1);
  --ring: rgb(37 99 235 / 0.28);
}

.dark {
  --accent: #7aa7ff;
  --accent-ink: #06142e;
  --accent-soft: rgb(122 167 255 / 0.14);
  --ring: rgb(122 167 255 / 0.3);
}

Radii

TokenDefaultUtilityUsed by
--radius-sm8pxrounded-smInputs, buttons, small controls
--radius-md12pxrounded-mdPopovers, menus, alerts, large buttons
--radius-lg16pxrounded-lgCards, tables, code blocks
--radius-xl22pxrounded-xlDialogs, big surfaces

Want sharper corners? Set the four values smaller (for example 4, 6, 8, 12). Want pills? Raise them. The interactive version of this is on the home page.

css
:root {
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-xl: 12px;
}

Shadows

Depth is a hairline plus a soft layered shadow, never a heavy drop shadow. Four tokens cover it.

UtilityUse
shadow-quietResting cards, inputs and controls.
shadow-liftHovered cards, menus, popovers.
shadow-popDialogs, sheets and the command palette.
shadow-insetA faint top highlight on filled buttons.
Show the shadow values

--shadow-quiet
light: 0 1px 1px rgb(27 26 23 / 0.04), 0 1px 3px rgb(27 26 23 / 0.05)
dark: 0 1px 1px rgb(0 0 0 / 0.3), 0 1px 3px rgb(0 0 0 / 0.3)

--shadow-lift
light: 0 1px 2px rgb(27 26 23 / 0.04), 0 8px 20px -6px rgb(27 26 23 / 0.1), 0 20px 40px -16px rgb(27 26 23 / 0.1)
dark: 0 1px 2px rgb(0 0 0 / 0.3), 0 10px 24px -6px rgb(0 0 0 / 0.45), 0 24px 48px -16px rgb(0 0 0 / 0.4)

--shadow-pop
light: 0 1px 2px rgb(27 26 23 / 0.06), 0 12px 32px -8px rgb(27 26 23 / 0.18), 0 32px 64px -24px rgb(27 26 23 / 0.22)
dark: 0 1px 2px rgb(0 0 0 / 0.4), 0 16px 40px -8px rgb(0 0 0 / 0.6), 0 40px 80px -24px rgb(0 0 0 / 0.55)

--shadow-inset
light: inset 0 1px 0 rgb(255 255 255 / 0.14)
dark: inset 0 1px 0 rgb(255 255 255 / 0.06)

Typography

TokenUtilityRole
--font-sansfont-sansInterface text (Geist)
--font-monofont-monoCode and numerals (Geist Mono)
--font-displayfont-displayHeadlines and brand moments (Instrument Serif)

Each falls back to a system stack when its variable is not set, so nothing breaks without fonts. To use your own typefaces, point the variables at them:

css
:root {
  --font-geist-sans: "Inter";
  --font-instrument: "Fraunces";
}

Add your own tokens

Add the variable for both themes, then register it so Tailwind generates a utility:

css
:root  { --success-surface: #e9f3ea; }
.dark  { --success-surface: #223127; }

@theme inline {
  --color-success-surface: var(--success-surface);
}
/* now: bg-success-surface */

Scoping a theme

Put class="dark" or class="light" on any element and everything inside renders in that palette, regardless of the page. Useful for a dark hero on a light page, an embedded preview, or a theme switcher that shows both at once.

tsx
<section className="dark rounded-xl bg-canvas p-8 text-ink">
  <Button>Always dark</Button>
</section>