shadcn/ui skill for Codex
Own your components, theme them with tokens. Codex treats shadcn/ui as source the project owns: it reads components.json first, adds components through the CLI, themes through paired CSS variables in OKLCH instead of hard-coded classes, and extends components with cva variants. It checks for local edits before overwriting, previews upstream changes with add --diff before applying them, and can publish a private registry for other repos.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- shadcn-ui Installs to ~/.agents/skills/shadcn-ui/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install shadcn-ui The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project shadcn-ui Installs to .agents/skills/shadcn-ui/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/shadcn-ui.zipunzip -o shadcn-ui.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) shadcn-ui PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $shadcn-ui, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Sets up and works with shadcn/ui in React projects - the shadcn CLI, components.json, Tailwind v4 CSS variable theming in OKLCH, adding and updating copied components, composing variants with cva, building forms, data tables and custom registries, and choosing Radix or Base UI primitives. Use when a user runs or asks about npx shadcn, wants to add a Button, Dialog, Form or DataTable, retheme components or add dark mode, update local components after an upstream change, or publish a private component registry.
Use it when
- A React project uses or is adopting shadcn/ui
- Components need a new variant, brand theme or dark mode
- Local shadcn components drifted and need upstream fixes
- Several apps should share one set of shadcn-style components
Use something else when
- Building an accessible primitive from scratch without shadcn styling: use radix-ui
- Designing the token architecture itself: use design-tokens
- Migrating Tailwind v3 config to v4 first: use tailwind-v4
How to check the result
Type check and lint pass, dark mode is correct inside portaled dialogs and menus, keyboard interaction works in each added component, and git diff on components/ui shows only intended edits.
The mistake it prevents
Running add with overwrite on components that carry local edits, or hard-coding colors in components instead of changing the CSS variable tokens.
Done well means: components live in the repo as owned source that matches the project’s conventions, theming is driven by CSS variables (not hard-coded colors in components), customizations happen through variants rather than one-off class soup, and the CLI can still add and diff components afterwards.
Facts to keep straight
- shadcn/ui is not a dependency you import; the CLI copies component source into your repo
(default
components/ui). You own and edit that code. components.jsondrives the CLI: style, Tailwind CSS file, aliases, icon library, and registries. Read it before running any command.- Current projects use Tailwind v4: theme tokens are CSS variables declared in the main CSS
file and mapped with
@theme inline; colors are OKLCH. Animations come fromtw-animate-css(the oldtailwindcss-animateplugin is for Tailwind v3 projects). - The primitive layer can be Radix (
radix-uiunified package) or Base UI; thestyleincomponents.jsonrecords which. Do not mix primitives inside one component. - CLI commands need network (they fetch from the registry). In a Codex sandbox, ask for approval before running them.
Before changing anything
- Read
AGENTS.md,components.json, the global CSS file it points to, andpackage.json. - List what is installed:
ls components/ui(or the alias path). Check for local edits before overwriting anything:git diff --stat -- components/ui. - Note the package manager; the CLI detects it from the lockfile.
Setup
# Existing app (Next.js, Vite, React Router, Astro, TanStack Start...)
npx shadcn@latest init
# Pick the primitive layer explicitly (create is an alias of init)
npx shadcn@latest init --base radix # or: --base base (Base UI)
Then add components by name; dependencies (other components, npm packages) come along:
npx shadcn@latest add button dialog dropdown-menu form
npx shadcn@latest add sidebar # blocks can pull many files: review the diff
add prompts before overwriting an existing file; never pass --overwrite on components
with local edits unless the user confirms.
Theming
/* app/globals.css (excerpt) */
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.55 0.2 264);
--primary-foreground: oklch(0.985 0 0);
--ring: oklch(0.55 0.2 264 / 50%);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.7 0.16 264);
--primary-foreground: oklch(0.145 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--radius-lg: var(--radius);
}
- Every surface token comes in a pair (
--card/--card-foreground). When adding a brand token, add both, define both modes, and map it in@theme inlinesobg-brandworks. - Check contrast of each foreground/background pair in both modes (4.5:1 for text).
- Dark mode: toggle the
.darkclass on<html>(for Next.js,next-themeswithattribute="class"andsuppressHydrationWarningon<html>). - Change the theme in CSS variables, not by editing
bg-primaryclasses across components.
Customizing components
| Change | Where |
|---|---|
| New look for a component everywhere | Edit components/ui/<name>.tsx (it is yours) |
| A new visual option | Add a variant to the component’s cva config |
| A one-off tweak at a call site | className prop; components merge with cn() |
Domain component (e.g. UserMenu) | New file outside components/ui that composes ui parts |
const buttonVariants = cva("inline-flex items-center gap-2 rounded-md text-sm font-medium ...", {
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
outline: "border bg-background hover:bg-accent",
success: "bg-emerald-600 text-white hover:bg-emerald-600/90", // added
},
size: { default: "h-9 px-4", sm: "h-8 px-3", lg: "h-10 px-6", icon: "size-9" },
},
defaultVariants: { variant: "default", size: "default" },
});
- Keep
components/uigeneric. Business logic in a primitive file makes upstream updates painful. - Use
asChild(Radix) to render aButtonas aLinkinstead of nesting interactive elements. - Components carry
data-slotattributes; style descendants with them ([&_[data-slot=icon]]:size-4) rather than brittle child selectors.
Forms and tables
- Forms: the shadcn form pieces wrap React Hook Form with a schema resolver (Zod is the common
default). Keep the schema next to the form, reuse it on the server. Deep form work belongs
to
$react-hook-form-zod. - Data tables:
npx shadcn@latest add tableand build on TanStack Table (@tanstack/react-table); the shadcn data-table guide is a pattern to copy, not a component. Keep column definitions in their own file and memoize data.
Updating components
npx shadcn@latest view button # inspect the current registry version
npx shadcn@latest add button --dry-run # what would be written
npx shadcn@latest add button --diff # diff registry version against the local file
Apply upstream fixes by hand when the local file has intentional edits. Commit before running
add --overwrite, so the change is reviewable as a git diff.
Private registry
For sharing components across repos:
- Create
registry.jsonlisting items withname,type(registry:ui,registry:block,registry:lib,registry:hook),files,dependenciesandregistryDependencies. npx shadcn@latest buildwrites JSON item files topublic/r/.- Host them; consumers add a namespace under
registriesincomponents.json(e.g."@acme": "https://ui.acme.dev/r/{name}.json", with headers for auth tokens read from env vars) and runnpx shadcn@latest add @acme/data-grid.
Never commit registry auth tokens into components.json; reference environment variables.
Gotchas
- Tailwind v3 projects need the v3 instructions (
tailwind.configwithhsl(var(--x))colors,tailwindcss-animate). Do not paste v4 CSS into a v3 project; migrate first with$tailwind-v4. - Path aliases in
components.jsonmust matchtsconfig.jsonpaths, or added files import from paths that do not resolve. - Mixing
@radix-ui/react-*packages and the unifiedradix-uipackage duplicates code;npx shadcn@latest migrate radixmoves imports to the unified package. - Dialog, Sheet and Popover portal to
body; theme classes must be on<html>or<body>, or portaled content renders unthemed. Toastwas replaced bySonnerin current registries; do not add both.
Verification
-
tsc --noEmitand the project’s lint pass; no unresolved@/components/ui/...imports. - Toggle dark mode on a page with dialog, dropdown and form; portaled content is themed.
- Keyboard pass: Tab, Escape, arrow keys work in every added interactive component.
-
git diff -- components/uishows only the edits you intended. - Run the axe check from
$accessibility-testingif the project has one.
Output
List components added or changed, new tokens/variants (with both mode values), files touched
outside components/ui, and any registry configuration.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "shadcn/ui"
short_description: "Own your components, theme them with tokens"
default_prompt: "$shadcn-ui Set up shadcn/ui in this Next.js app and add button, dialog and form" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.