---
title: "shadcn/ui: Theming"
slug: "shadcn-theming"
category: "frontend"
tags: ["frontend", "shadcn", "theming", "custom-properties", "dark-mode", "tailwind", "design-system"]
status: "stable"
last_updated: 2026-08-14
summary: "Edit CSS variables in globals.css to retheme shadcn; the current CLI default is oklch tokens with no tailwind.config; never hardcode colors inside component files."
related: ["[[frontend/shadcn]]", "[[frontend/shadcn-installation]]", "[[frontend/shadcn-composition]]", "[[frontend/tailwind]]", "[[frontend/css-custom-properties]]", "[[frontend/tailwind-dark-mode]]", "[[frontend/css]]"]
---

## Overview

shadcn/ui theming lives entirely in CSS variables declared on `:root` and `.dark` in `globals.css`. Every component references those variables; none hardcode a color. Change the variables, and every component updates. This page covers how to work within that system: adjusting the palette, adding brand tokens, enabling dark mode, and avoiding the escape hatches that break the model.

## Edit tokens in `globals.css`, not in component files

The `--background`, `--foreground`, `--primary`, `--ring`, and other semantic tokens are declared once in `globals.css`. Components use utilities like `bg-background` and `text-primary` that map to those tokens. To retheme, change the tokens. Do not open a component file to patch a color class.

```css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.55 0.18 264);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.3);
  --destructive-foreground: oklch(0.985 0 0);
  --border: oklch(0.922 0 0);
  --ring: oklch(0.55 0.18 264);
  --radius: 0.625rem;
}
```

The current CLI default writes full `oklch()` color functions and maps them into Tailwind with `@theme inline` (no `tailwind.config` file). A project scaffolded before the Tailwind v4 switch may still carry bare HSL channels (`--primary: 221.2 83.2% 53.3%`) composed as `hsl(var(--primary) / <alpha-value>)` in `tailwind.config.ts`. Both formats work; pick one per project and do not mix them. See [[frontend/css-custom-properties]] for the general pattern and [[frontend/shadcn-installation]] for the CLI defaults.

## Define dark mode with a `.dark` class selector, not `prefers-color-scheme`

shadcn uses the class strategy: add `.dark` to the `<html>` element to activate dark mode. This gives users runtime control without CSS specificity fights.

```css
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --primary: oklch(0.7 0.18 264);
  --primary-foreground: oklch(0.205 0 0);
  --border: oklch(1 0 0 / 10%);
  --ring: oklch(0.7 0.18 264);
}
```

Toggle dark mode in React with a state value on the root element:

```tsx
document.documentElement.classList.toggle("dark", isDark);
```

Pair this with `localStorage` persistence and a server-rendered cookie so the class is set before paint. A flash of wrong theme is a layout shift that hurts CLS. See [[frontend/tailwind-dark-mode]] for the Tailwind class-strategy configuration.

## Map tokens to a custom brand palette with oklch

Replace the default palette with your brand colors as oklch values. Most design tools export hex; convert to oklch before pasting (most color pickers and browser DevTools do this conversion natively now).

```css
:root {
  /* Brand: Indigo 600 ≈ oklch(0.51 0.23 276) */
  --primary: oklch(0.51 0.23 276);
  --primary-foreground: oklch(1 0 0);

  /* Brand: Slate 900 for text */
  --foreground: oklch(0.21 0.03 265);
}
```

oklch's lightness channel is perceptually uniform: moving from `oklch(0.55 ...)` to `oklch(0.7 ...)` looks like the same visual step regardless of hue, which makes generating a dark-mode counterpart mechanical instead of a fresh pick. On a legacy HSL-token project, the same edit uses raw channel triples (`--primary: 243 75% 59%`) instead. Use a contrast checker to verify that `--primary-foreground` is legible on `--primary`. The WCAG AA minimum is 4.5:1 for normal text. A brand color that fails contrast should be lightened or darkened; do not adjust the token to cheat the check.

## Add custom tokens for components that fall outside the semantic set

The shadcn token set covers interactive semantics. When a page layout, a chart, or a data-dense component needs colors outside that set, add new variables rather than reusing semantics with wrong meaning.

```css
:root {
  --chart-1: oklch(0.65 0.22 41);
  --chart-2: oklch(0.6 0.12 185);
  --chart-3: oklch(0.4 0.07 227);
  --sidebar-background: oklch(0.21 0 0);
  --sidebar-foreground: oklch(0.97 0 0);
}
```

Name tokens by semantic role, not by visual value. `--sidebar-background` is good; `--gray-950` is an internal detail. With Tailwind v4, register the token in the `@theme inline` block so it becomes a utility. See [[frontend/tailwind]] for the v4 CSS-first config.

## Check browser support before committing fully to oklch

oklch reached Baseline across evergreen browsers in 2023 and is safe to ship in 2026 without a fallback. When mixing oklch and legacy HSL tokens during a migration, keep the `@theme inline` (or `tailwind.config.ts extend.colors`) mapping consistent with whichever format each token actually uses; do not silently convert one without updating the other.

## Avoid overriding colors inside component files

Patching a color directly in `button.tsx` or `card.tsx` works once. It fails when the theme changes, when dark mode toggles, or when another developer edits the token. The rule is: if a style is a theme decision, it belongs in `globals.css`. If it is a one-off layout adjustment, use a Tailwind utility class passed via `className` on the callsite rather than editing the shared file.

The exception is when a component genuinely needs a new variant that no token covers. In that case, add the variant to the component file using `cva` (class-variance-authority) and document the decision. See [[frontend/shadcn-composition]] for the variant-extension pattern.

## Related

- [[frontend/shadcn]]
- [[frontend/shadcn-installation]]
- [[frontend/shadcn-composition]]
- [[frontend/tailwind]]
- [[frontend/css-custom-properties]]
- [[frontend/tailwind-dark-mode]]
- [[frontend/tailwind-theme]]
