Toronto —Let’s talk

—/Tool

shadcn/ui theme generator with a live preview.

Pick a neutral, a brand colour and a radius. Get all 31 colour tokens for light and dark, in OKLCH.

Copy a globals.css that drops straight into Tailwind v4.

01/Your theme

Start from
Neutral
Brand colour
Indigooklch(0.511 0.262 276.966) · #4f39f6
0.511
0.262
277°
Primary
Accent (hover surfaces)
Radius (rem)
Charts

02/Preview

03/Tokens

31 colours · light / dark

  • background#ffffff / #09090b
  • foreground#09090b / #fafafa
  • card#ffffff / #18181b
  • card-foreground#09090b / #fafafa
  • popover#ffffff / #18181b
  • popover-foreground#09090b / #fafafa
  • primary#4f39f6 / #5e5afb
  • primary-foreground#fafafa / #fafafa
  • secondary#f4f4f5 / #27272a
  • secondary-foreground#18181b / #fafafa
  • muted#f4f4f5 / #27272a
  • muted-foreground#71717b / #9f9fa9
  • accent#f4f4f5 / #27272a
  • accent-foreground#18181b / #fafafa
  • destructive#e40016 / #ff6568
  • border#e4e4e7 / white 10%
  • input#e4e4e7 / white 15%
  • ring#4f39f6 / #7d87ff
  • chart-1#4f39f6 / #605eff
  • chart-2#bdc6ff / #d5dcff
  • chart-3#605dff / #7982ff
  • chart-4#280094 / #3d00d5
  • chart-5#8a96ff / #a5b1ff
  • sidebar#fafafa / #18181b
  • sidebar-foreground#09090b / #fafafa
  • sidebar-primary#4f39f6 / #5e5afb
  • sidebar-primary-foreground#fafafa / #fafafa
  • sidebar-accent#f4f4f5 / #27272a
  • sidebar-accent-foreground#18181b / #fafafa
  • sidebar-border#e4e4e7 / white 10%
  • sidebar-ring#4f39f6 / #7d87ff
  • How to readLight mode left, dark mode right

04/Contrast

WCAG 2 · AA is 4.5 for text, 3 for focus rings

PairLightDark
Body textforeground on background19.89AA19.05AA
Card textcard-foreground on card19.89AA16.98AA
Primary buttonprimary-foreground on primary6.17AA4.63AA
Secondary buttonsecondary-foreground on secondary16.11AA14.26AA
Muted textmuted-foreground on background4.83AA7.56AA
Muted text on mutedmuted-foreground on muted4.39Below 4.55.66AA
Accent (hover)accent-foreground on accent16.11AA14.26AA
Destructive textdestructive on background4.89AA6.94AA
Sidebar primarysidebar-primary-foreground on sidebar-primary6.17AA4.63AA
Focus ringring on background6.44Pass6.39Pass

05/globals.css

/* shadcn/ui theme: zinc · indigo oklch(0.511 0.262 276.966) · brand primary · neutral accent · brand charts · radius 0.625rem */
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}

:root {
  --radius: 0.625rem;
  --background: oklch(1 0 0);
  --foreground: oklch(0.141 0.005 285.823);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.141 0.005 285.823);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.141 0.005 285.823);
  --primary: oklch(0.511 0.262 276.966);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.967 0.001 286.375);
  --secondary-foreground: oklch(0.21 0.006 285.885);
  --muted: oklch(0.967 0.001 286.375);
  --muted-foreground: oklch(0.552 0.016 285.938);
  --accent: oklch(0.967 0.001 286.375);
  --accent-foreground: oklch(0.21 0.006 285.885);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.92 0.004 286.32);
  --input: oklch(0.92 0.004 286.32);
  --ring: oklch(0.511 0.262 276.966);
  --chart-1: oklch(0.511 0.262 276.966);
  --chart-2: oklch(0.84 0.08 276.966);
  --chart-3: oklch(0.58 0.233 276.966);
  --chart-4: oklch(0.32 0.198 276.966);
  --chart-5: oklch(0.71 0.153 276.966);
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.141 0.005 285.823);
  --sidebar-primary: oklch(0.511 0.262 276.966);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.967 0.001 286.375);
  --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
  --sidebar-border: oklch(0.92 0.004 286.32);
  --sidebar-ring: oklch(0.511 0.262 276.966);
}

.dark {
  --background: oklch(0.141 0.005 285.823);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.21 0.006 285.885);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.21 0.006 285.885);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.571 0.232 276.966);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.274 0.006 286.033);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.274 0.006 286.033);
  --muted-foreground: oklch(0.705 0.015 286.067);
  --accent: oklch(0.274 0.006 286.033);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.671 0.176 276.966);
  --chart-1: oklch(0.581 0.232 276.966);
  --chart-2: oklch(0.9 0.049 276.966);
  --chart-3: oklch(0.66 0.183 276.966);
  --chart-4: oklch(0.42 0.26 276.966);
  --chart-5: oklch(0.78 0.113 276.966);
  --sidebar: oklch(0.21 0.006 285.885);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: oklch(0.571 0.232 276.966);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.274 0.006 286.033);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.671 0.176 276.966);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}

06/About

Output
globals.css for shadcn/ui on Tailwind v4
Colour space
OKLCH, kept inside sRGB
Tokens
31 colours × light and dark, plus radius
Privacy
Generated in your browser, nothing stored

How the theme generator works

shadcn/ui components don’t hard-code colours. Every button, card and menu reads from a small set of CSS variables: --background, --primary, --muted, --ring and so on. Change those variables and every component follows. This tool writes the whole set from a handful of decisions and shows the result on a real layout before you commit to it.

What each choice controls

  • Neutral sets the greys: backgrounds, cards, borders, muted text. Zinc, slate, gray, stone and neutral are Tailwind’s own scales. Tinted takes zinc’s lightness and adds a trace of your brand hue.
  • Brand colour drives the focus ring and the charts, and the primary button if you choose. Adjust it with lightness, chroma and hue sliders. OKLCH keeps lightness even across hues.
  • Primary fills buttons, checkboxes and switches with the brand colour, or keeps shadcn’s near-black default.
  • Accent is the hover surface behind ghost buttons and menu items, not a brand colour. Tinted gives it a faint wash of the brand hue.
  • Radius sets --radius. Components derive their own corners from it, from 4px below it to 4px above.

Contrast checks

Every pair of text and fill the components actually use is checked against WCAG 2: 4.5:1 for text, 3:1 for the focus ring. Text on a filled button automatically picks whichever end of the neutral scale reads better. If the brand colour is too light for a focus ring on white, the ring is darkened until it passes. One thing to know: shadcn’s own default puts muted text on the muted surface at about 4.4:1, just under AA. The check shows it rather than hiding it.

How to use the output

  1. Set up shadcn/ui in a Tailwind v4 project with npx shadcn@latest init.
  2. Replace the contents of app/globals.css (or your main CSS file) with the generated file.
  3. The file maps --font-sans to Geist, as the Next.js starter does. Change those two lines if you use another font.

Note

Colours are mapped into sRGB, so the hex values match what most screens show. A few very saturated presets lose a little chroma to fit. The first line of the file records the settings, so you can recreate the theme later.