Skip to main content
Introducing packages.sweber.dev
Documentation menushadcn/ui theme

shadcn/ui theme

One brand color to the semantic colors of shadcn/ui, with contrast that holds in light and dark mode.

shadcn/ui themes are a list of semantic variables: --background, --primary, --muted-foreground and so on. Most generators pick them by eye. Gradient picks them from the palette steps, so the contrast promises carry over.

npx @sweberdev/gradient "#e30613" --format shadcn --out app/globals.css

The file contains the variables for :root and .dark, and the @theme inline block that maps them to Tailwind v4 colors, so bg-primary and text-muted-foreground work. Replace the theme section of your globals.css with it. Dark mode uses the .dark class, as shadcn/ui does; --dark media, --dark both and --dark light-dark work too.

What goes where

VariableStepGuaranteed
backgroundneutral 50
foreground, card-foreground, popover-foregroundneutral 9007:1 on background and card
card, popoverwhite (light), neutral 100 (dark)
primary / primary-foregroundbrand 600 / its text color4.5:1
secondary / secondary-foregroundneutral 100 / neutral 9004.5:1
muted / muted-foregroundneutral 100 / neutral 7004.5:1, also on background
accent / accent-foregroundbrand 100 / brand 9004.5:1
destructive / destructive-foregrounddanger 600 / its text color4.5:1
borderneutral 200decorative
inputneutral 5003:1 on the page, so form fields meet WCAG 1.4.11
ringbrand 5003:1
chart-1 to chart-5chart colors3:1

The danger scale is derived from your brand when the palette has none. To use your own red, pass it in code:

import { createPalette, toShadcn } from "@sweberdev/gradient";

const palette = createPalette({ brand: "#e30613" }, { status: { danger: "#d4351c" } });
toShadcn(palette, { format: "hex" });

Options

toShadcn(palette, options?) takes format ("oklch" default, or "hex"), dark ("class" default), darkSelector and theme (false leaves out the @theme inline block). shadcnTokens(palette) returns the values as hex per mode, for your own tooling.

The default shadcn/ui input border is a light grey that fails 3:1 on white. Gradient's input is darker on purpose. If you prefer the lighter look, override --input after the generated block.