Skip to content
All designer tools

shadcn Theme Editor

Tune the tokens against real components in both modes, then install the result with one command

Install this theme

The whole theme is in the URL — nothing is stored on our side
npx shadcn@latest add "https://hoverlab.dev/r/theme.json?t=e30"
Brand hue250°

Moves primary, ring, accent tint, the sidebar and the chart wheel together.

Brand chroma0.190

How saturated the accent is. Above about 0.24 the chart colours start colliding for a dichromat.

Radius0.625rem

--radius. The sm / md / lg / xl steps are derived from it.

Neutral tint0.006

How far the greys lean toward the brand hue. A little goes a long way.

Individual tokens

All derived from the sliders

Surfaces

The page, and the two things that sit on top of it.

  • --background
  • --foreground
  • --card
  • --card-foreground
  • --popover
  • --popover-foreground
Brand

Primary is the only token most people ever change.

  • --primary
  • --primary-foreground
  • --ring
Supporting

Secondary, muted and accent are all neutrals at rest.

  • --secondary
  • --secondary-foreground
  • --muted
  • --muted-foreground
  • --accent
  • --accent-foreground
Destructive & edges

Destructive stays red across every hue — a warning that shifts with the brand stops reading as a warning.

  • --destructive
  • --destructive-foreground
  • --border
  • --input
Charts

Spread by hue and separated by lightness, so the series survive colour blindness and greyscale print.

  • --chart-1
  • --chart-2
  • --chart-3
  • --chart-4
  • --chart-5
Sidebar

Its own surface set, so a sidebar can sit a step away from the page.

  • --sidebar
  • --sidebar-foreground
  • --sidebar-primary
  • --sidebar-primary-foreground
  • --sidebar-accent
  • --sidebar-accent-foreground
  • --sidebar-border
  • --sidebar-ring

Keep this theme. This browser remembers what you last had open. A free account remembers the ones you name, on every machine you use.

light

Project settings

Muted text, which is where most themes quietly fail AA.

Live
Public registry
SecondaryOutlineDestructiveAccent
Series 1Series 2Series 3Series 4Series 5
Overview
Billing

Popover surface

Sidebar and popover

dark

Project settings

Muted text, which is where most themes quietly fail AA.

Live
Public registry
SecondaryOutlineDestructiveAccent
Series 1Series 2Series 3Series 4Series 5
Overview
Billing

Popover surface

Sidebar and popover

Text contrast, both modes

20 pairs clear AA

Every text-on-surface pair in this theme clears 4.5:1 in both light and dark — body text, cards, popovers, all four button variants, the sidebar and the muted helper text that most themes get wrong.

Chart colours under colour blindness

all distinct

The five series stay five colours under protanopia, deuteranopia and tritanopia. The lightnesses are solved onto the hues rather than fixed, which is what makes that hold as you turn the hue.

Same model as the simulator. A legend should still carry a second cue — no palette makes colour alone sufficient.

cssglobals.css
/* A shadcn/ui theme, generated by Hoverlab — hoverlab.dev/tools/shadcn
   Replace the :root, .dark and @theme inline blocks in your globals.css. */

:root {
  --radius: 0.625rem;
  --background: oklch(1.000 0.000 0.0);
  --foreground: oklch(0.145 0.006 250.0);
  --card: oklch(1.000 0.000 0.0);
  --card-foreground: oklch(0.145 0.006 250.0);
  --popover: oklch(1.000 0.000 0.0);
  --popover-foreground: oklch(0.145 0.006 250.0);
  --primary: oklch(0.520 0.190 250.0);
  --primary-foreground: oklch(0.985 0.006 250.0);
  --ring: oklch(0.708 0.076 250.0);
  --secondary: oklch(0.970 0.006 250.0);
  --secondary-foreground: oklch(0.205 0.006 250.0);
  --muted: oklch(0.970 0.006 250.0);
  --muted-foreground: oklch(0.545 0.006 250.0);
  --accent: oklch(0.970 0.006 250.0);
  --accent-foreground: oklch(0.205 0.006 250.0);
  --destructive: oklch(0.577 0.245 27.3);
  --destructive-foreground: oklch(0.985 0.000 0.0);
  --border: oklch(0.922 0.006 250.0);
  --input: oklch(0.922 0.006 250.0);
  --chart-1: oklch(0.660 0.200 250.0);
  --chart-2: oklch(0.400 0.200 322.0);
  --chart-3: oklch(0.530 0.200 34.0);
  --chart-4: oklch(0.790 0.200 106.0);
  --chart-5: oklch(0.920 0.200 178.0);
  --sidebar: oklch(0.985 0.006 250.0);
  --sidebar-foreground: oklch(0.145 0.006 250.0);
  --sidebar-primary: oklch(0.520 0.190 250.0);
  --sidebar-primary-foreground: oklch(0.985 0.006 250.0);
  --sidebar-accent: oklch(0.970 0.006 250.0);
  --sidebar-accent-foreground: oklch(0.205 0.006 250.0);
  --sidebar-border: oklch(0.922 0.006 250.0);
  --sidebar-ring: oklch(0.708 0.076 250.0);
}

.dark {
  --background: oklch(0.145 0.006 250.0);
  --foreground: oklch(0.985 0.006 250.0);
  --card: oklch(0.205 0.006 250.0);
  --card-foreground: oklch(0.985 0.006 250.0);
  --popover: oklch(0.205 0.006 250.0);
  --popover-foreground: oklch(0.985 0.006 250.0);
  --primary: oklch(0.720 0.190 250.0);
  --primary-foreground: oklch(0.145 0.006 250.0);
  --ring: oklch(0.556 0.076 250.0);
  --secondary: oklch(0.269 0.006 250.0);
  --secondary-foreground: oklch(0.985 0.006 250.0);
  --muted: oklch(0.269 0.006 250.0);
  --muted-foreground: oklch(0.708 0.006 250.0);
  --accent: oklch(0.269 0.006 250.0);
  --accent-foreground: oklch(0.985 0.006 250.0);
  --destructive: oklch(0.704 0.191 22.2);
  --destructive-foreground: oklch(0.145 0.006 250.0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --chart-1: oklch(0.680 0.200 250.0);
  --chart-2: oklch(0.440 0.200 322.0);
  --chart-3: oklch(0.560 0.200 34.0);
  --chart-4: oklch(0.800 0.200 106.0);
  --chart-5: oklch(0.920 0.200 178.0);
  --sidebar: oklch(0.205 0.006 250.0);
  --sidebar-foreground: oklch(0.985 0.006 250.0);
  --sidebar-primary: oklch(0.720 0.190 250.0);
  --sidebar-primary-foreground: oklch(0.145 0.006 250.0);
  --sidebar-accent: oklch(0.269 0.006 250.0);
  --sidebar-accent-foreground: oklch(0.985 0.006 250.0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.556 0.076 250.0);
}

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

Replace the :root, .dark and @theme inline blocks. The last one is the part people forget — in Tailwind v4 the variables alone give you nothing, because bg-primary only exists once that block maps it.

jsonregistry-item.json
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme",
  "type": "registry:theme",
  "title": "Custom theme",
  "description": "A light and dark shadcn/ui token set, generated at hoverlab.dev/tools/shadcn.",
  "cssVars": {
    "light": {
      "radius": "0.625rem",
      "background": "oklch(1.000 0.000 0.0)",
      "foreground": "oklch(0.145 0.006 250.0)",
      "card": "oklch(1.000 0.000 0.0)",
      "card-foreground": "oklch(0.145 0.006 250.0)",
      "popover": "oklch(1.000 0.000 0.0)",
      "popover-foreground": "oklch(0.145 0.006 250.0)",
      "primary": "oklch(0.520 0.190 250.0)",
      "primary-foreground": "oklch(0.985 0.006 250.0)",
      "ring": "oklch(0.708 0.076 250.0)",
      "secondary": "oklch(0.970 0.006 250.0)",
      "secondary-foreground": "oklch(0.205 0.006 250.0)",
      "muted": "oklch(0.970 0.006 250.0)",
      "muted-foreground": "oklch(0.545 0.006 250.0)",
      "accent": "oklch(0.970 0.006 250.0)",
      "accent-foreground": "oklch(0.205 0.006 250.0)",
      "destructive": "oklch(0.577 0.245 27.3)",
      "destructive-foreground": "oklch(0.985 0.000 0.0)",
      "border": "oklch(0.922 0.006 250.0)",
      "input": "oklch(0.922 0.006 250.0)",
      "chart-1": "oklch(0.660 0.200 250.0)",
      "chart-2": "oklch(0.400 0.200 322.0)",
      "chart-3": "oklch(0.530 0.200 34.0)",
      "chart-4": "oklch(0.790 0.200 106.0)",
      "chart-5": "oklch(0.920 0.200 178.0)",
      "sidebar": "oklch(0.985 0.006 250.0)",
      "sidebar-foreground": "oklch(0.145 0.006 250.0)",
      "sidebar-primary": "oklch(0.520 0.190 250.0)",
      "sidebar-primary-foreground": "oklch(0.985 0.006 250.0)",
      "sidebar-accent": "oklch(0.970 0.006 250.0)",
      "sidebar-accent-foreground": "oklch(0.205 0.006 250.0)",
      "sidebar-border": "oklch(0.922 0.006 250.0)",
      "sidebar-ring": "oklch(0.708 0.076 250.0)"
    },
    "dark": {
      "background": "oklch(0.145 0.006 250.0)",
      "foreground": "oklch(0.985 0.006 250.0)",
      "card": "oklch(0.205 0.006 250.0)",
      "card-foreground": "oklch(0.985 0.006 250.0)",
      "popover": "oklch(0.205 0.006 250.0)",
      "popover-foreground": "oklch(0.985 0.006 250.0)",
      "primary": "oklch(0.720 0.190 250.0)",
      "primary-foreground": "oklch(0.145 0.006 250.0)",
      "ring": "oklch(0.556 0.076 250.0)",
      "secondary": "oklch(0.269 0.006 250.0)",
      "secondary-foreground": "oklch(0.985 0.006 250.0)",
      "muted": "oklch(0.269 0.006 250.0)",
      "muted-foreground": "oklch(0.708 0.006 250.0)",
      "accent": "oklch(0.269 0.006 250.0)",
      "accent-foreground": "oklch(0.985 0.006 250.0)",
      "destructive": "oklch(0.704 0.191 22.2)",
      "destructive-foreground": "oklch(0.145 0.006 250.0)",
      "border": "oklch(1 0 0 / 10%)",
      "input": "oklch(1 0 0 / 15%)",
      "chart-1": "oklch(0.680 0.200 250.0)",
      "chart-2": "oklch(0.440 0.200 322.0)",
      "chart-3": "oklch(0.560 0.200 34.0)",
      "chart-4": "oklch(0.800 0.200 106.0)",
      "chart-5": "oklch(0.920 0.200 178.0)",
      "sidebar": "oklch(0.205 0.006 250.0)",
      "sidebar-foreground": "oklch(0.985 0.006 250.0)",
      "sidebar-primary": "oklch(0.720 0.190 250.0)",
      "sidebar-primary-foreground": "oklch(0.145 0.006 250.0)",
      "sidebar-accent": "oklch(0.269 0.006 250.0)",
      "sidebar-accent-foreground": "oklch(0.985 0.006 250.0)",
      "sidebar-border": "oklch(1 0 0 / 10%)",
      "sidebar-ring": "oklch(0.556 0.076 250.0)"
    }
  }
}

What the install command above fetches. Commit it to your own repo if you would rather serve the theme yourself — it is a plain registry:theme item and the CLI does not care where it comes from.

Now use it on something

Every effect, block, page and template in the catalog is styled against tokens shaped exactly like these — so they take your values rather than fighting them.

Hand these tokens to an agent

npx shadcn@latest add https://hoverlab.dev/r/hoverlab.json

More designer tools

All 36 run in the tab — no account, no upload.See all