---
name: shadcn-component-builder
description: "Build custom shadcn/ui components — Radix primitives, CVA variants, theming, dark mode, a11y. Builds custom shadcn/ui-style components: Radix primitive composition, class-variance-authority for variants, theme tokens (CSS vars), dark mode, ARIA attributes, focus management, and the registry.json pattern for shipping your own component library."
license: MIT
---

# shadcn Component Builder

Build production-grade components in the shadcn/ui style: Radix primitives wrapped with Tailwind, variants driven by `class-variance-authority`, theme tokens exposed as CSS variables, and full keyboard/ARIA support. This skill is for developers who use shadcn/ui as a foundation but need components that don't ship in the default registry, or who want to package their own components for distribution.

## When to use it

- You need a component the shadcn registry doesn't have (a multi-step wizard, a data table cell editor, a split button) and want it to match the existing token system.
- You're extending an existing component with new `variant`/`size` options and want the CVA config done correctly.
- You want to ship an internal component library other repos can pull with `npx shadcn add`, and need a valid `registry.json` / `registry-item.json`.
- A component "works" but fails accessibility: missing focus traps, no `aria-*` wiring, broken keyboard nav.

## How it works

Components are composed from `@radix-ui/react-*` primitives (Dialog, Popover, DropdownMenu, etc.) using the `asChild` + `Slot` pattern so styling stays on your element. Variants are defined with `cva()` and merged through the `cn()` helper (`clsx` + `tailwind-merge`) to avoid class conflicts. Theming uses the shadcn CSS-variable convention — `--background`, `--foreground`, `--primary`, `--ring`, etc. in HSL — with the `.dark` class providing the dark palette. For distribution, the skill generates registry entries that declare `type`, `dependencies`, `registryDependencies`, and `files`.

## Example

```tsx
// components/ui/badge.tsx
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring",
  {
    variants: {
      variant: {
        default: "border-transparent bg-primary text-primary-foreground",
        outline: "text-foreground",
        destructive: "border-transparent bg-destructive text-destructive-foreground",
      },
    },
    defaultVariants: { variant: "default" },
  }
)

export function Badge({ className, variant, ...props }:
  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof badgeVariants>) {
  return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
```

## Tips & gotchas

- Always pass `className` last into `cn()` so consumer overrides win — `tailwind-merge` only resolves conflicts in order.
- Define theme colors as raw HSL channels (`222.2 47.4% 11.2%`), not `hsl(...)`, since Tailwind wraps them as `hsl(var(--primary))`. Putting `hsl()` in the variable breaks opacity modifiers like `bg-primary/50`.
- Use `forwardRef` on interactive components — Radix passes refs through, and omitting it breaks `asChild` composition and focus management.
- In `registry.json`, list shadcn deps (e.g. `"button"`) under `registryDependencies` and npm deps under `dependencies`; mixing them causes install failures.

## FAQ

**Does this replace the shadcn CLI?** No. It generates components and registry files; you still use `npx shadcn add` / `init` to install them. The two are complementary.

**Tailwind v4?** Yes — it adapts to the `@theme` inline token syntax when your project uses v4, or the `tailwind.config` + `:root` CSS-var approach for v3.

**Can I keep my existing tokens?** Yes. It reads your current CSS variables and generates variants against them rather than overwriting your palette.
