Siteplane UI

Color Picker

Choose a hex color with a swatch, hue slider, and channel fields.

Markdown

Used for buttons, links and focus rings.

"use client";

import { ColorPicker } from "@/siteplane/components/color-picker";
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Brand color</FieldLabel>
      <ColorPicker defaultValue="#6366f1" />
      <FieldDescription>
        Used for buttons, links and focus rings.
      </FieldDescription>
    </Field>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/color-picker

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/form @siteplane/input

Field is installed with Input.

Usage

import { ColorPicker } from "@/siteplane/components/color-picker";

<ColorPicker aria-label="Brand color" />;

Controlled color

The callback emits a lowercase hex string.

"use client";

import { ColorPicker } from "@/siteplane/components/color-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";
import { useState } from "react";

export default function Example() {
  const [color, setColor] = useState("#6366f1");
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Brand color</FieldLabel>
      <ColorPicker value={color} onValueChange={setColor} />
    </Field>
  );
}

Sizes and swatch-only trigger

Hide the text value with showValue={false}; keep a meaningful accessible label. disabled blocks the picker and leaves its value out of form submission.

"use client";

import { ColorPicker } from "@/siteplane/components/color-picker";

const sizes = [
  { name: "small", size: "sm" },
  { name: "medium", size: "md" },
  { name: "large", size: "lg" },
  { name: "extra large", size: "xl" },
] as const;

export default function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {sizes.map(({ name, size }) => (
        <ColorPicker
          key={size}
          aria-label={`Brand color, ${name}`}
          size={size}
          defaultValue="#6366f1"
          showValue={false}
        />
      ))}
      <ColorPicker aria-label="Accent color" defaultValue="#6366f1" disabled />
    </div>
  );
}

Custom color swatch

Set variant="swatch" to draw the trigger as a Color Swatch Picker swatch, for a palette that ends in a custom color. Pressing the swatch opens the picker. Pass selected while the custom color is the one chosen in the row, and indicator to show the selection as a check or a ring, as on Color Swatch Picker. onOpenChange lets the row choose the custom color as soon as the picker opens.

"use client";

import { ColorPicker } from "@/siteplane/components/color-picker";
import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import { useState } from "react";

const presets = [
  { value: "#6366f1", label: "Indigo", color: "#6366f1" },
  { value: "#22c55e", label: "Green", color: "#22c55e" },
  { value: "#f59e0b", label: "Amber", color: "#f59e0b" },
  { value: "#f43f5e", label: "Rose", color: "#f43f5e" },
];

export default function Example() {
  const [color, setColor] = useState("#6366f1");
  const [custom, setCustom] = useState("#0ea5e9");
  const isPreset = presets.some((preset) => preset.value === color);

  return (
    <div className="flex flex-wrap items-center gap-2">
      <ColorSwatchPicker
        aria-label="Accent color"
        options={presets}
        size="sm"
        value={color}
        onValueChange={setColor}
      />
      <ColorPicker
        aria-label={`Custom color ${custom.toUpperCase()}`}
        selected={!isPreset}
        size="sm"
        value={custom}
        variant="swatch"
        onOpenChange={(open) => {
          if (open) setColor(custom);
        }}
        onValueChange={(value) => {
          setCustom(value);
          setColor(value);
        }}
      />
    </div>
  );
}

Form integration

Inside a Field, the picker submits its lowercase hex value under the field’s name. It always holds a color, so required never blocks submission.

"use client";

import { Button } from "@/siteplane/components/button";
import { ColorPicker } from "@/siteplane/components/color-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";
import { Form } from "@/siteplane/components/form";

export default function Example() {
  return (
    <Form
      className="grid w-full max-w-sm gap-4"
      onSubmit={(event) => {
        event.preventDefault();
      }}
    >
      <Field name="brandColor">
        <FieldLabel>Brand color</FieldLabel>
        <ColorPicker defaultValue="#22c55e" />
      </Field>
      <Button type="submit">Save</Button>
    </Form>
  );
}

API reference

Component properties
PropTypeDefaultDescription
value / defaultValuestringdefaultValue: "#000000"Controlled color or uncontrolled initial hex; an omitted hash is accepted.
onValueChange(value: string) => void—Emits lowercase hex.
open / defaultOpenbooleanuncontrolled / falseControlled or initial open state of the picker.
onOpenChange(open: boolean) => void—Called when the picker opens or closes.
showValuebooleantrueDisplays the current value beside the swatch. The swatch variant never shows it.
variantdefault | swatchdefaultdefault is an outlined button with the color and its hex value. swatch is a bare swatch of the current color, drawn like a Color Swatch Picker swatch.
selectedbooleanfalseSwatch variant only: marks the swatch as the chosen color of its row. It shows the indicator and sets aria-current.
indicatorcheck | ringcheckSwatch variant only: how a selected swatch shows it, as on Color Swatch Picker.
sizesm | md | lg | xlprovider / lgControl dimensions.
disabledbooleanfalseDisables trigger, fields, and submitted value; a disabled Field also disables the picker.
name / formstring—Hidden input name and associated form id; a Field name takes precedence.
readOnlybooleanfalseShows and submits the color; the controls open for reading and copying, but the color does not change.
requiredbooleanfalseSets required on the hidden form input. The picker always holds a color, so it never blocks submission.
id / className / style / tabIndexstring / string / CSSProperties / number—Applied to the visible trigger.
aria-label / aria-labelledby / aria-describedby / aria-invalidstring / string / string / AriaAttributes[aria-invalid]Generated color label if unnamedApplied to the trigger; inside a Field, FieldLabel, FieldDescription and FieldError are connected automatically.
labelsPartial<ColorPickerLabels>EnglishAccessible names of the controls in the popover; see the keys below.

The keys of labels, each with its English default, are controls (Color controls, the popover), saturation (Color saturation and brightness), hue (Color hue), hex (Hex color value), copy (Copy hex value), copied (Copied hex value, while the check shows), copyFailed (Copy unavailable, when the clipboard refuses), and red, green and blue (R color channel, G color channel, B color channel). The letters R, G and B beside the channel fields stay as they are.

Exported types: ColorPickerProps, ColorPickerLabels. These describe the contracts above.

Read the installed source.

Usage notes

Inside a Field, FieldLabel names the trigger and clicking it focuses the trigger; outside a Field, pass aria-label or aria-labelledby. The picker takes only the props above; it owns its hidden form input. Use readOnly to show a color that cannot change and disabled to switch the picker off. Invalid hex falls back to the normalized default, then black. This control does not expose alpha, locale, or popup placement props.