Chroma Panel

Search documentation

Find a page or section

Use the built-in color input, or let shadcn/ui own the popover.

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

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.

"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.

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.

.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 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.

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 for focus and Escape-key behavior.