Theming
The tokens every component is styled with, and how dark mode switches them.
The theme sets shadcn's standard color variables, so other shadcn components match. It lives in your CSS, and changing a token there changes every component that uses it.
Dark mode
Every token has a dark value, used inside any element with the dark class, where Tailwind's
dark: variant applies too. The theme also sets color-scheme: dark there, so scrollbars and
native form controls follow.
Put the class on <html> to switch the whole page. It has to be there before the page first
paints, or a dark page flashes light as it loads, so a script at the top of <head> sets it from
the reader's stored choice, or from the system setting until they make one. A provider then
stores each new choice and, while it's System, follows the system setting as it changes.
Below are setups for Next.js and Vite. For other frameworks, such as Astro, Remix and TanStack Start, follow shadcn's dark mode guides, which set the same class.
Next.js
next-themes does both. Install it.
Wrap the root layout in its ThemeProvider, and add suppressHydrationWarning to <html>,
since its script changes the class before React hydrates.
disableTransitionOnChange turns transitions off for the moment the theme switches, so controls
change color at once rather than fading one by one.
Vite
Add the script to <head> in index.html, before anything that renders. It falls back to the
system setting when nothing valid is stored, and leaves the page alone if storage is blocked.
Then add a provider, and wrap the app in it.
It stores the choice under theme, the key next-themes uses too.
Toggle
A Dropdown Menu with a radio item for each choice lets readers pick
one, and shows which is set. The trigger's icon follows the page with the dark: variant, so it's
right before the provider knows the choice. Import useTheme from next-themes in Next.js, or
from @/components/theme-provider with the Vite provider. Both return the theme and setTheme
it uses.
Tokens
Alongside shadcn's variables, the theme adds these.
Colors
Every color token in light and dark mode. Values such as grey-50 are steps of the
palette. Hover a swatch to see the color it resolves to.
Surfaces
Text and icons
Borders
Controls
Overlays
Overlays are translucent. primary-hover and primary-pressed are shown over primary.
States
Syntax
Colors for code, such as the keys and values in JSON Viewer. They're GitHub Light's and GitHub Dark's, as in the code blocks on this site.
Charts
Sidebar
Palette
These are the same in both modes. Each step is a variable, such as --palette-blue-600.
Type scale
text-base stays at 16px, so inputs can use text-base md:text-sm to keep iOS from zooming in.