Docs
Theming
Tokens
72 variables: surfaces, text levels, brand and status tones (each with a soft variant), elevation, stacking layers and motion. Tailwind maps them to utilities like bg-surface, text-muted-foreground, bg-success-soft and shadow-floating. Left light, right dark:
Surfaces
--backgroundoklch(1.0000 0 0) / oklch(0 0 0)--surface#FFFFFF / #1A1A1A--card#EBEBEB / oklch(0.1822 0 0)--popoveroklch(1.0000 0 0) / #262626--primary#000 / oklch(0.9850 0 0)
Text and lines
--foregroundoklch(0.3211 0 0) / oklch(0.9219 0 0)--muted-foregroundoklch(0.5200 0.0234 264.3637) / oklch(0.7155 0 0)--borderoklch(0.9276 0.0058 264.5313) / oklch(0.3715 0 0)--ringoklch(0.6000 0.2000 36.9340) / oklch(0.6617 0.2215 36.9340)
Tones
--brand#D63F00--success#15803D--warning#F5A524--destructive#DC2626--info#2563EB
Setup
npx wingo-ui@latest init merges the tokens into your global stylesheet, and add adds any a new component needs. Only missing names are added; values you already have stay. The full block is at /r/tokens.css (and in the MCP tool get_theme_tokens):
app/globals.css
:root {
--background: oklch(1.0000 0 0);
--foreground: oklch(0.3211 0 0);
--card: #EBEBEB;
--card-foreground: oklch(0.3211 0 0);
--popover: oklch(1.0000 0 0);
--popover-foreground: oklch(0.3211 0 0);
--surface: #FFFFFF;
--surface-foreground: oklch(0.3211 0 0);
--primary: #000;
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.9670 0.0029 264.5419);
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
--muted: oklch(0.9846 0.0017 247.8389);
--muted-foreground: oklch(0.5200 0.0234 264.3637);
--accent: oklch(0.9640 0.0230 45.0000);
--accent-foreground: oklch(0.5200 0.1700 40.0000);
--destructive: #DC2626;
--destructive-foreground: oklch(1.0000 0 0);
--destructive-soft: #FDEDED;
--destructive-soft-foreground: #B42318;
--brand: #D63F00;
--brand-foreground: #FFFFFF;
--brand-soft: #FFEFE6;
--brand-soft-foreground: #B03400;
/* … */
}Dark mode
- Class (recommended): dark values in
.darkplus@custom-variant dark (&:is(.dark *));, toggled by next-themes withattribute="class". - System only: when your stylesheet already uses
@media (prefers-color-scheme: dark), the CLI puts the dark values there instead.
Theme generator
Pick a brand color, radius, font and component defaults in the theme generator, preview them on real components, then export theme.json and apply it:
npx wingo-ui@latest theme apply ./theme.jsonIt replaces the values of the tokens the theme sets (light and dark) and adds the Tailwind mappings for new ones. It prints the component defaults and locale for UIProvider.
Component defaults
UIProvider (item ui-config, installed with every component) sets app-wide defaults per component and the locale for dates and numbers. Explicit props always win.
// app/layout.tsx
import { ThemeProvider } from "next-themes"
import { UIProvider } from "@/lib/ui-config"
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<UIProvider defaults={{ button: { radius: "full" }, input: { variant: "filled" } }} locale="en-US">
{children}
</UIProvider>
</ThemeProvider>Rules of thumb
- Semantic tokens only: no hex values or arbitrary colors in app code.
- Neutral by default; the brand tone for the one main action on a screen.
- Colored text uses the soft foreground tokens (
text-success-soft-foreground) for contrast in both themes. - Check every change in light and dark mode.