# React Components

You are working in a React 19 project that uses shadcn/ui components with Tailwind CSS v4 and TypeScript. The components are source files you own, added by the shadcn CLI. Write code for the versions in `package.json` and the conventions below, not the shadcn/ui of your training data: `forwardRef`, `hsl(var(--x))` and `tailwind.config.js` are gone.

## Before you write code

- Read `components.json`. `style` names the primitive library by its prefix (`radix-nova`, `base-vega`; an older `new-york` project is Radix). `tailwind.css` is the stylesheet that holds the tokens. `iconLibrary` decides icon imports (`lucide` is `lucide-react`, `tabler` is `@tabler/icons-react`): never assume lucide. `aliases` give the import paths. With `rsc: true`, interactive components need `"use client"`.
- List the UI folder (the `ui` alias, usually `components/ui`). Do not import a component that was never added, and do not add one twice.
- Open a component's file before using it: its props, variants and parts are whatever that file says, including local changes.
- `npx shadcn@latest info` prints the resolved config; `npx shadcn@latest docs <name>` gives a component's documentation URLs. Use the project's runner (`pnpm dlx`, `bunx --bun`) instead of `npx` when it has one.

## Adding and updating components

Add with `npx shadcn@latest add button card dialog`. Other registries are namespaced in `components.json` (private ones read tokens from environment variables) and installed by name, as in `npx shadcn@latest add @acme/data-table`:

```json
{
  "registries": {
    "@acme": "https://acme.com/r/{name}.json",
    "@private": {
      "url": "https://registry.example.com/r/{name}.json",
      "headers": { "Authorization": "Bearer ${REGISTRY_TOKEN}" }
    }
  }
}
```

- When the user does not name a registry, ask; never guess one.
- After adding from a third-party registry, read every new file: rewrite imports to the project's aliases, swap icons to the project's library, and fix missing parts.
- To update a component the project has changed, preview with `add <name> --dry-run` and `--diff <file>`, then merge by hand. Never `--overwrite` without asking.

## Writing components

- Write function components that take `ComponentProps<"div">` (or the primitive's props type) and accept `ref` as an ordinary prop: React 19 needs no `forwardRef`.
- Put `data-slot="<part-name>"` on every part's root element, mirror variants as `data-variant` and `data-size`, and merge `className` last through `cn()` (`twMerge(clsx(…))` from `lib/utils`).
- Define variants with `cva` and export them, so links and other elements can reuse the look (`buttonVariants`). Where the project already uses `tailwind-variants`, use `tv` the same way.

```tsx
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";

const statVariants = cva("flex flex-col gap-1 rounded-lg border p-4", {
  variants: {
    tone: {
      default: "bg-card text-card-foreground",
      muted: "bg-muted text-muted-foreground",
    },
  },
  defaultVariants: { tone: "default" },
});

function Stat({ className, tone = "default", ...props }: ComponentProps<"div"> & VariantProps<typeof statVariants>) {
  return <div data-slot="stat" data-variant={tone} className={cn(statVariants({ tone }), className)} {...props} />;
}

export { Stat, statVariants };
```

- Build from parts, not from props: `Card` with `CardHeader` (`CardTitle`, `CardDescription`, `CardAction`), `CardContent` and `CardFooter`, each exported. Style a part by its relatives with `data-slot` selectors (`has-data-[slot=card-action]:grid-cols-[1fr_auto]`) and named groups (`group/card`).
- Reuse before writing markup: `Alert` for callouts, `Empty` for empty states, `Skeleton` for placeholders, `Badge` for labels, `Separator` instead of `<hr>`, `Spinner` for progress.

## Radix or Base UI

shadcn/ui ships the same components on two primitive libraries. Use the one the project's files import, and never mix their APIs.

- Radix (`import { Dialog as DialogPrimitive } from "radix-ui"`, or the older `@radix-ui/react-*` packages; `Slot.Root` for `asChild`): `<DialogTrigger asChild><Button>Open</Button></DialogTrigger>`.
- Base UI (`@base-ui/react/<component>`): `<DialogTrigger render={<Button />}>Open</DialogTrigger>`. When `render` produces something other than a `<button>`, add `nativeButton={false}`: `<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>Pick a date</PopoverTrigger>`.
- A link that looks like a button is a link: `<Link href="/docs" className={buttonVariants({ variant: "outline" })}>Docs</Link>` on either library, or `<Button asChild><Link href="/docs">Docs</Link></Button>` on Radix. On Base UI, an `<a>` rendered through Button gets `role="button"` unless the project's Button file handles links; check it before writing `render={<a />}`.
- Other differences: Base UI's Select takes `items` on the root and positions with `alignItemWithTrigger`, Radix's uses `position="popper"`; Base UI's Accordion and ToggleGroup take `multiple` with array values where Radix takes `type="single"`; Base UI's Slider takes a number for one thumb, Radix an array; Base UI marks state as `data-open` or `data-checked`, Radix as `data-state="open"`.
- Never install the other library to get a prop, and never put `asChild` on a Base UI component or `render` on a Radix one.

## Tokens, theme and layout

- Use semantic tokens only: `bg-background text-foreground`, `bg-card`, `bg-popover`, `bg-primary text-primary-foreground`, `bg-secondary`, `bg-muted text-muted-foreground`, `bg-accent`, `text-destructive`, `border-border`, `border-input`, `ring-ring`, `bg-sidebar`, `chart-1` to `chart-5`. Never palette colors (`bg-gray-100`, `text-blue-600`) or hex values, and no `dark:` color pairs: tokens switch under `.dark`.
- Tokens live in the stylesheet from `components.json`: `oklch(…)` values in `:root` and `.dark`, mapped to utilities in `@theme inline` (`--color-primary: var(--primary)`). Use them as `var(--primary)`, never `hsl(var(--primary))`.
- To add a token, define it in both blocks and map it, then use it:

```css
:root { --warning: oklch(0.84 0.16 84); --warning-foreground: oklch(0.28 0.07 46); }
.dark { --warning: oklch(0.41 0.11 46); --warning-foreground: oklch(0.99 0.02 95); }
@theme inline {
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
}
```

- Dark mode is the `.dark` class on `<html>` (next-themes with `attribute="class"` in Next.js) with `@custom-variant dark (&:is(.dark *));`. Radius comes from `--radius` (`rounded-lg` is `var(--radius)`, smaller sizes derive from it).
- `className` on a shadcn component is for layout (width, margin, grid placement). Change its look with a variant, a token or a new variant in its file.
- Lay out with `flex` or `grid` and `gap-*`, not `space-y-*`; `size-*` for equal width and height; `truncate`. Never add `z-index` to dialogs, popovers, menus or tooltips.

## Icons

- Import icons from the `iconLibrary` package. lucide-react 1.x renders icons `aria-hidden` unless you give them a label, and no longer has brand icons.
- Inside Button, menu items and other components, add no size classes: the component sizes its `svg`. Mark a button icon's side with `data-icon="inline-start"` or `"inline-end"` where the project's Button supports it. Size standalone icons with `size-4`.
- Icon-only buttons use `size="icon"` and an `aria-label` on the button. Pass icons as components (`icon={CheckIcon}`), not as strings mapped to icons.

## Forms and feedback

- Build forms with the Field components: `FieldGroup` > `Field` > `FieldLabel htmlFor`, the control, `FieldDescription`, `FieldError`. Group checkboxes and radios in `FieldSet` with `FieldLegend`; use `orientation="horizontal"` for settings rows.
- Mark state on both: `data-invalid` on `Field` and `aria-invalid` on the control; `data-disabled` on `Field` and `disabled` on the control.
- With React Hook Form, render each control through `Controller` into a `Field` (`data-invalid={fieldState.invalid}`, `<FieldError errors={[fieldState.error]} />`) with a Zod resolver. Do not build new forms with the older `Form`, `FormField`, `FormItem` and `FormMessage` wrappers.
- Inputs with buttons or icons use `InputGroup` with `InputGroupInput` and `InputGroupAddon`, not absolute positioning. Two to seven options use `ToggleGroup`.
- Toasts are Sonner: render the project's `<Toaster />` once in the root layout, then `toast.success("Saved")`, `toast.error(…)` or `toast.promise(…)` from `sonner`.
- Loading buttons compose: `<Button disabled><Spinner data-icon="inline-start" />Saving</Button>`. Button has no `isLoading` prop.

## Accessibility and direction

- Every `Dialog`, `Sheet`, `Drawer` and `AlertDialog` has a title (`className="sr-only"` if it must be hidden). Use `AlertDialog` for destructive confirmations.
- Keep items inside their group (`SelectItem` in `SelectGroup`, `DropdownMenuItem` in `DropdownMenuGroup`, `CommandItem` in `CommandGroup`), `TabsTrigger` inside `TabsList`, and an `AvatarFallback` in every `Avatar`.
- Keep the visible focus styles (`focus-visible:ring-[3px] focus-visible:ring-ring/50`) when you restyle, and use buttons and links for anything clickable, never a `div` with `onClick`.
- Use logical utilities (`ms-*`, `pe-*`, `start-*`, `text-start`, `rounded-s-*`, `border-e`) and `rtl:rotate-180` on directional icons, so components work under `dir="rtl"`.

## Do not

- Do not write `forwardRef`, `hsl(var(--x))` tokens, a `tailwind.config.js`, or `tailwindcss-animate` (v4 projects use `tw-animate-css`).
- Do not copy component code from memory or from GitHub: add it with the CLI, then edit it.
- Do not override component colors with `className`, hard-code palette colors, or add `dark:` color classes.
- Do not mix Radix and Base UI APIs, or wrap a trigger's child in an extra `div` instead of using `asChild` or `render`.
- Do not build string-mapped icon registries, custom toast systems or hand-rolled modals when the component exists.

## Before you finish

- Run the type checker and the linter and fix what they report.
- Check every component you touched in light and dark mode, with the keyboard alone (focus visible, Escape closes, focus returns), and at a phone width.
- Report which components you added or changed in `components/ui`, so the reviewer knows what diverges from upstream.
