AI Skill Library

shadcn/ui & Radix UI

Headless accessible components, shadcn patterns, theming, variants with cva.

reactuicomponentsfrontend
# shadcn/ui & Radix UI

## Setup shadcn/ui
```bash
npx shadcn@latest init
npx shadcn@latest add button card dialog dropdown-menu
```

## Component variants with cva
```tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
  // base classes
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: { variant: 'default', size: 'default' },
  }
)

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />
}
```

## Radix UI primitives (headless)
```tsx
import * as Dialog from '@radix-ui/react-dialog'
import * as Select from '@radix-ui/react-select'
import * as Tooltip from '@radix-ui/react-tooltip'

// Dialog
<Dialog.Root open={open} onOpenChange={setOpen}>
  <Dialog.Trigger asChild><Button>Open</Button></Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out" />
    <Dialog.Content className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] max-w-md rounded-lg bg-background p-6 shadow-xl">
      <Dialog.Title>Title</Dialog.Title>
      <Dialog.Description>Description</Dialog.Description>
      <Dialog.Close asChild><Button variant="ghost">Close</Button></Dialog.Close>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>
```

## CSS variables theming
```css
/* globals.css */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --primary-foreground: 210 40% 98%;
  --muted: 210 40% 96.1%;
  --radius: 0.5rem;
}
.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 217.2 91.2% 59.8%;
}
/* Usage */
bg-background  =>  hsl(var(--background))
```

## cn() utility
```ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
// Merges Tailwind classes correctly:
cn('px-2 py-1', 'p-4')  // -> 'p-4' (not 'px-2 py-1 p-4')
```

## Compound component pattern
```tsx
const Card = ({ children, className }) => (
  <div className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)}>{children}</div>
)
Card.Header = ({ children }) => <div className="flex flex-col space-y-1.5 p-6">{children}</div>
Card.Title = ({ children }) => <h3 className="text-2xl font-semibold">{children}</h3>
Card.Content = ({ children }) => <div className="p-6 pt-0">{children}</div>
Card.Footer = ({ children }) => <div className="flex items-center p-6 pt-0">{children}</div>

// Usage
<Card>
  <Card.Header><Card.Title>Hello</Card.Title></Card.Header>
  <Card.Content>Content here</Card.Content>
</Card>
```

API: /api/skills/shadcn-radix-ui