| name | shadcn-theme-default |
| description | Enforces the default shadcn/ui Neutral theme (black/white/gray) with OKLCH CSS variables, Tailwind v4 integration, and dark mode support |
| user-invocable | true |
shadcn/ui Default Theme — Neutral (Black/White/Gray)
You are a frontend engineer responsible for applying and maintaining the default shadcn/ui Neutral theme across the project. When creating components, pages, layouts, or any visual element, you MUST use the theme tokens defined below. Never hardcode hex, RGB, or HSL values — always reference CSS variables via Tailwind utility classes.
This skill only modifies CSS and Tailwind configuration files. It never reads or modifies .env, .env.local, or credential files.
Planning Protocol (MANDATORY — execute before ANY action)
Before modifying any styling file or component, you MUST complete this planning phase:
-
Understand the request. Determine what visual change is needed: new component styling, theme adjustment, dark mode fix, or full theme setup.
-
Survey the current state. Check: (a) src/app/globals.css (or equivalent) for existing CSS variables, (b) tailwind.config.ts or @theme directives for Tailwind integration, (c) components.json for shadcn/ui configuration, (d) whether dark mode is already configured. Do NOT read .env or credential files.
-
Build an execution plan. Write out which files will be created or modified and in what order. Theme variables must be set before component styling.
-
Identify risks. Flag: (a) overwriting custom theme values the user may have set, (b) breaking existing component styles by changing variable names, (c) Tailwind version incompatibility (v3 uses hsl(), v4 uses oklch()).
-
Execute sequentially. Apply changes in order: CSS variables first, then Tailwind config, then component updates.
-
Summarize. Report what changed and confirm both light and dark modes render correctly.
Do NOT skip this protocol.
Theme Architecture
shadcn/ui uses CSS custom properties (variables) following a background/foreground naming convention:
- The background variable (e.g.,
--primary) is used for the element's fill/background.
- The foreground variable (e.g.,
--primary-foreground) is used for text/icons on top of that background.
In Tailwind, these map to:
bg-primary uses var(--primary)
text-primary-foreground uses var(--primary-foreground)
The color space is OKLCH (Oklab Lightness Chroma Hue), which is perceptually uniform and the default in shadcn/ui since Tailwind v4.
Complete CSS Variables — Default Neutral Theme
Light Mode (:root)
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
}
Dark Mode (.dark)
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.269 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( / );
: ( / );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( / );
: ( );
}
Tailwind v4 Integration
In Tailwind v4, register the CSS variables as colors using the @theme inline directive. Add this to your main CSS file after the variable definitions:
@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-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-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
: (--chart-);
: (--chart-);
: (--sidebar);
: (--sidebar-foreground);
: (--sidebar-primary);
: (--sidebar-primary-foreground);
: (--sidebar-accent);
: (--sidebar-accent-foreground);
: (--sidebar-border);
: (--sidebar-ring);
: ((--radius) - );
: ((--radius) - );
: (--radius);
: ((--radius) + );
}
Tailwind v3 (legacy)
If the project uses Tailwind v3, the variables use HSL format instead of OKLCH. Add to tailwind.config.ts:
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
: {
: ,
: ,
},
: ,
: ,
: ,
},
: {
: ,
: ,
: ,
},
},
},
};
config;
Dark Mode Setup
Next.js (App Router) with next-themes
npm install next-themes
Create src/components/shared/theme-provider.tsx:
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
Wrap the app in src/app/layout.tsx:
import { ThemeProvider } from "@/components/shared/theme-provider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
);
}
Theme Toggle Component
"use client";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
aria-label="Toggle theme"
>
<span className="sr-only">Toggle theme</span>
{/* Sun icon for light, Moon icon for dark */}
<svg
className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy= = />
);
}
Nuxt / SvelteKit / Other Frameworks
For non-Next.js frameworks, toggle the .dark class on the <html> element:
function toggleDarkMode() {
document.documentElement.classList.toggle("dark");
const isDark = document.documentElement.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
}
function initTheme() {
const stored = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (stored === "dark" || (!stored && prefersDark)) {
document.documentElement.classList.add("dark");
}
}
Available shadcn/ui Components
The following components are available and all respect the theme tokens above. Install via npx shadcn@latest add <component>:
Accordion, Alert, Alert Dialog, Aspect Ratio, Avatar, Badge, Breadcrumb, Button, Button Group, Calendar, Card, Carousel, Chart, Checkbox, Collapsible, Combobox, Command, Context Menu, Data Table, Date Picker, Dialog, Drawer, Dropdown Menu, Empty, Field, Hover Card, Input, Input Group, Input OTP, Item, Kbd, Label, Menubar, Native Select, Navigation Menu, Pagination, Popover, Progress, Radio Group, Resizable, Scroll Area, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Sonner, Spinner, Switch, Table, Tabs, Textarea, Toast, Toggle, Toggle Group, Tooltip, Typography.
Tailwind Utility Class Reference
Use these Tailwind classes to apply theme tokens. NEVER use arbitrary values like bg-[#000] or text-[#fff].
Backgrounds
| Purpose | Class |
|---|
| Page background | bg-background |
| Card surface | bg-card |
| Popover surface | bg-popover |
| Primary fill (CTA buttons) | bg-primary |
| Secondary fill (secondary buttons) | bg-secondary |
| Muted fill (disabled, subtle) | bg-muted |
| Accent fill (hover, highlight) | bg-accent |
| Destructive fill (delete, error) | bg-destructive |
| Sidebar background | bg-sidebar |
Text
| Purpose | Class |
|---|
| Body text | text-foreground |
| Text on primary bg | text-primary-foreground |
| Text on secondary bg | text-secondary-foreground |
| Muted/placeholder text | text-muted-foreground |
| Text on accent bg | text-accent-foreground |
| Text on destructive bg | text-destructive-foreground |
| Text on card bg | text-card-foreground |
Borders
| Purpose | Class |
|---|
| Standard border | border-border |
| Input border | border-input |
| Focus ring | ring-ring |
| Sidebar border | border-sidebar-border |
Border Radius
| Purpose | Class |
|---|
| Small radius | rounded-sm (calc(var(--radius) - 4px)) |
| Medium radius | rounded-md (calc(var(--radius) - 2px)) |
| Large radius | rounded-lg (var(--radius) = 0.625rem) |
| Extra large radius | rounded-xl (calc(var(--radius) + 4px)) |
Component Styling Patterns
Standard Button Variants
<Button>Save changes</Button>
<Button variant="secondary">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Edit</Button>
<Button variant="ghost">More</Button>
<Button variant="link">Learn more</Button>
Card Pattern
<div className="rounded-lg border border-border bg-card p-6 text-card-foreground shadow-sm">
<h3 className="text-lg font-semibold text-foreground">Title</h3>
<p className="text-sm text-muted-foreground">Description text</p>
</div>
Input Pattern
<input
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
placeholder="Enter value..."
/>
Alert/Badge Pattern
{}
<span className="inline-flex items-center rounded-md bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground">
Active
</span>
{}
<span className="inline-flex items-center rounded-md bg-destructive px-2.5 py-0.5 text-xs font-medium text-destructive-foreground">
Error
</span>
Full globals.css Template
This is the complete globals.css file for a project using the default Neutral theme:
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
}
{
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( / );
: ( / );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( );
: ( / );
: ( );
}
inline {
: (--background);
: (--foreground);
: (--card);
: (--card-foreground);
: (--popover);
: (--popover-foreground);
: (--primary);
: (--primary-foreground);
: (--secondary);
: (--secondary-foreground);
: (--muted);
: (--muted-foreground);
: (--accent);
: (--accent-foreground);
: (--destructive);
: (--destructive-foreground);
: (--border);
: (--input);
: (--ring);
: (--chart-);
: (--chart-);
: (--chart-);
: (--chart-);
: (--chart-);
: (--sidebar);
: (--sidebar-foreground);
: (--sidebar-primary);
: (--sidebar-primary-foreground);
: (--sidebar-accent);
: (--sidebar-accent-foreground);
: (--sidebar-border);
: (--sidebar-ring);
: ((--radius) - );
: ((--radius) - );
: (--radius);
: ((--radius) + );
}
base {
* {
border-border;
}
{
bg-background text-foreground;
}
}
components.json (shadcn/ui config)
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
Rules for the Agent
-
ALWAYS use theme tokens. Never write bg-black, text-white, bg-gray-100, border-gray-200, or any hardcoded color class. Use bg-background, text-foreground, bg-muted, border-border instead.
-
Respect the background/foreground pairing. If you use bg-primary, the text on top must be text-primary-foreground. Never mix pairs (e.g., never use text-foreground on a bg-primary surface).
-
Dark mode is automatic. The CSS variables handle the switch. Never write conditional dark mode classes like dark:bg-gray-900 — the variables already flip values.
-
Use the radius tokens. Instead of rounded-lg with a fixed value, use the radius variables: rounded-sm, rounded-md, rounded-lg, rounded-xl.
-
Chart colors follow the sequence. For multi-series charts, use chart-1 through chart-5 in order.
-
Sidebar uses its own token set. When styling sidebar components, use bg-sidebar, text-sidebar-foreground, border-sidebar-border, etc. — not the generic tokens.
-
Detect Tailwind version. Check package.json for Tailwind version. If v4+, use OKLCH variables with @theme inline. If v3, use HSL variables with tailwind.config.ts extension.
-
Installing new components. Use npx shadcn@latest add <component>. The CLI respects components.json and generates components with the correct theme tokens.