# shadcn/ui

> Add a React color picker to a shadcn/ui project, match its Tailwind theme tokens, or place ChromaPanel inside the popover your app already uses.

Source: https://chroma-panel.jscrate.dev/react/integrations/shadcn-ui
Last updated: 2026-09-21

chroma-panel works beside shadcn/ui without a provider or adapter. Use
`ColorInput` for the quickest setup. Use `ChromaPanel` when your shadcn/ui
popover, dialog, sheet, or form should own the surrounding UI.

## Install

```bash
npm install chroma-panel
```

The package is independent of shadcn/ui, Radix UI, and Base UI. It does not add
another copy of those libraries to your app.

## Use the built-in popover

`ColorInput` already handles its trigger, positioning, focus, mobile bottom
sheet, and hidden form value.

```tsx
"use client";

import { useState } from "react";
import { ColorInput } from "chroma-panel";

export function BrandColorField() {
  const [color, setColor] = useState("#2563eb");

  return (
    <ColorInput
      value={color}
      onChange={(next) => setColor(next.hex)}
      classNames={{
        trigger:
          "h-9 w-12 rounded-md border border-input bg-background shadow-xs",
      }}
      aria-label="Brand color"
    />
  );
}
```

The trigger accepts Tailwind classes through `classNames.trigger`. The panel
parts use the same pattern; see [styling with
Tailwind](https://chroma-panel.jscrate.dev/react/handbook/tailwind).

## Match the shadcn/ui theme

Map the panel variables to the tokens already defined by your shadcn/ui theme.
This keeps light mode, dark mode, borders, and focus rings in sync.

```css
.cp-root {
  --cp-surface: var(--popover);
  --cp-surface-raised: var(--muted);
  --cp-border: var(--border);
  --cp-text: var(--popover-foreground);
  --cp-text-muted: var(--muted-foreground);
  --cp-accent: var(--primary);
  --cp-focus: var(--ring);
  --cp-radius-lg: var(--radius);
}
```

If your theme values use OKLCH, they can be passed through unchanged. The
[theming guide](https://chroma-panel.jscrate.dev/react/handbook/theming) lists every `--cp-*` variable.

## Use a shadcn/ui popover or dialog

Do not place `ColorInput` inside another popover: that creates two overlays and
two focus managers. Put the inline `ChromaPanel` inside the shadcn/ui
`PopoverContent`, `DialogContent`, or `SheetContent` instead.

```tsx
import { ChromaPanel } from "chroma-panel";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";

<Popover>
  <PopoverTrigger asChild>
    <Button variant="outline">Choose color</Button>
  </PopoverTrigger>
  <PopoverContent className="w-auto border-0 bg-transparent p-0 shadow-none">
    <ChromaPanel defaultValue="#2563eb" />
  </PopoverContent>
</Popover>;
```

shadcn/ui can be configured with different primitives. If your generated
`PopoverTrigger` uses `render` instead of `asChild`, keep the same composition
and follow the API in your local `popover.tsx` file.

Inside a modal or sheet, an inline panel is usually simpler. See [color picker
in a modal](https://chroma-panel.jscrate.dev/react/recipes/in-a-modal) for focus and Escape-key behavior.
