Overview
MCP Server
Custom Shadcn Color Picker for React and Tailwind CSS. A shadcn color picker with a saturation area, hue, opacity and channel sliders, a hue wheel, HEX, RGB, HSL, HSB and OKLCH fields, swatches and an eye dropper.
import {
ColorPicker,
ColorPickerArea,
ColorPickerContent,
ColorPickerFields,
ColorPickerPanel,
ColorPickerSlider,
ColorPickerSwatches,
Browse 15 production-ready Shadcn Color Picker components for dashboards, forms, and product UI. These examples use Base UI primitives from @base-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.
Browse all 15 Shadcn Color Picker components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the ReUI library.
<ColorPicker value={color} onValueChange={setColor} alpha>
<ColorPickerTrigger />
<ColorPickerContent>
<ColorPickerPanel>
<ColorPickerArea />
<ColorPickerSlider channel="hue" />
<ColorPickerSlider channel="alpha" />
<ColorPickerFields />
<ColorPickerSwatches colors={["#ef4444", "#22c55e", "#3b82f6"]} />
</ColorPickerPanel>
</ColorPickerContent>
</ColorPicker>Hand your agent this brief or the ReUI MCP, not a screenshot. Free to install.
When to use
When not to use
Agent notes
ColorPicker renders no wrapper element, so its children lay out in the parent; className on it styles only the trigger it draws when given no childrenparseColor reads is accepted, but the next change comes back written in the root formatalpha, opacity in a passed value is ignored on screen and every value the picker emits is opaquehttps://reui.io/docs/components/base/color-picker.mdget_component("color-picker")npx shadcn@latest add @reui/color-pickerThe hue wheel, channel sliders, recent colors, contrast checker, brand scale and the other examples are on the Color Picker page.
Color Picker is composable. ColorPicker holds the value and renders no
wrapper element. ColorPickerTrigger or ColorPickerInput opens
ColorPickerContent, and ColorPickerPanel renders the same controls in
place. Inside either, mix the area, sliders, wheel, fields, swatches and
actions in any layout. It composes the shadcn Popover, Button, InputGroup and
DropdownMenu, and does its own color math, so there is no color library to
install.
The value is a CSS color string, written in format: #3b82f6 by default.
null or "" is empty. A value from outside can be any color parseColor
reads, in any format; the picker shows it, and the next change is written in
format.
OKLCH is written to 0.01% lightness, 5 decimals of chroma and 3 of hue, so
hex to OKLCH and back lands on the same color. A swatch picked in an
"oklch" picker stays selected after a reload.
onValueChange fires on every change, each move of a drag included, and only
when the written string changes. onValueCommit fires once an interaction
ends: a drag released, a key pressed, a field or the input read, a swatch,
the eye dropper or Clear. Save to a server or an undo history there.
const [color, setColor] = useState<string | null>("#3b82f6")
<ColorPicker
value={color}
onValueChange={setColor}
onValueCommit={(value) => saveBrandColor(value)}
/>The picker keeps the channels a color was edited in, not only the string it
emits. Drag to a gray and the hue slider stays where you left it; set OKLCH
chroma to 0 and back and the hue returns. An OKLCH value outside sRGB, such as
Tailwind v4's oklch(62.3% 0.214 259.815), round-trips verbatim in "oklch"
format. The other formats, and the area and sliders, map it into sRGB the way
CSS Color 4 does: chroma comes down at the same lightness and hue until
clipping the rest is not noticeable, so the hex matches what a browser paints
on an sRGB screen. lab(), lch(), oklab() and a wide-gamut color() are
kept as OKLCH the same way.
Without alpha, the value is always opaque: an incoming alpha is ignored and
the opacity slider and field are left out.
type ColorPickerColor = {
space: "rgb" | "hsl" | "hsv" | "oklch"
channels: [number, number, number]
alpha: number
}useColorPicker and the helpers hand you that parsed color. rgb runs 0 to
255; hsl and hsv take hue 0 to 360 and the rest 0 to 100; oklch takes
lightness 0 to 100, chroma 0 to 0.4 and hue 0 to 360. alpha is 0 to 1.
Every string the picker renders comes from i18n. It is merged over the
English defaults per section, so one label is enough.
const DE: ColorPickerI18nOverrides = {
labels: {
placeholder: "Farbe wählen",
hue: "Farbton",
alpha: "Deckkraft",
eyeDropper: "Farbe vom Bildschirm aufnehmen",
},
}
<ColorPicker i18n={DE} />The root. Holds the value and the popover state and renders no wrapper
element. Without children it renders a ColorPickerTrigger and a
ColorPickerContent with the default panel, so <ColorPicker /> alone is a
complete picker; className and placeholder reach that trigger.
A shadcn Button that opens the popover, and the element that takes the root's
id and ARIA props. It shows a ColorPickerSwatch, the value and a chevron.
children replace all three, for a swatch-only or icon trigger. Accepts every
Button prop.
The value as text, in format with hex in capitals, or the placeholder, in a
span. Accepts every span prop.
A shadcn InputGroup with a swatch button that opens the popover and a field
you can type in. It reads everything parseColor reads, plus what only a
page can resolve, on blur and Enter: color-mix(), relative colors,
light-dark() and var(), so var(--primary) takes your theme's primary and
var(--x, red) its fallback. A CSS-wide keyword, currentColor or a var()
with no value is rejected rather than read as black. Text it cannot read is
dropped and the value restored. An empty field clears the value. Accepts every
input prop except value and defaultValue.
The shadcn PopoverContent, aligned to the start and w-72 wide, or with
alpha w-84 (w-96 below md, where the fields' text is 16px), capped to
the viewport. Without children
it renders ColorPickerPanel with the default controls. Accepts every
PopoverContent prop, align, side and className included.
The controls in a column, spaced per style. Without children: the area, the
eye dropper and a copy button either side of the hue slider (and the opacity
slider with alpha), the fields and the root's swatches. Put it straight
inside ColorPicker for an inline picker. Accepts every div prop.
The saturation and brightness square at the current hue, h-40 and full
width. Saturation runs left to right and brightness bottom to top. Accepts
every div prop; size it with className.
One channel as a gradient track. The track shows what the channel can reach
from the current color, so dragging red redraws green and blue. Accepts every
div prop.
colorSpace="oklch" with "lightness", "chroma" and "hue" gives
perceptual sliders, the ones the OKLCH theme token example uses.
A hue ring, size-48. Children fill the square inscribed in its hole, so a
ColorPickerArea with className="size-full" there gives the ring-and-square
picker. --color-picker-wheel-thickness sets the ring's width. Accepts every
div prop.
A format menu and the value's channels as joined fields in a shadcn
InputGroup, divided by full-height shadcn Separators: 1 hex field, or 3
channels, plus opacity with alpha, which keeps its own width at the end
while hex or the channels take the rest. Each menu item shows the color in
that format. The shown format is the fields' own and never changes what
onValueChange emits, but it is what a copy takes.
One channel as a borderless field, for your own InputGroup, sized by its
widest value. Text is read on blur and Enter: one number with a
comma or dot decimal. % is a share of the channel's range as in CSS, so 50%
red is 127.5 and 50% chroma 0.2, and a hue takes deg, rad, grad or
turn and wraps, so -30 is 330. A number field is a spinbutton with its
range. Accepts every input prop except value and defaultValue.
The fields of OKLCH with opacity need about 290px, or 312px below md where
the text is 16px. The popover's opacity width fits both; in a narrower inline
panel the widest values clip.
<InputGroup>
<InputGroupAddon>R</InputGroupAddon>
<ColorPickerChannelInput channel="red" />
</InputGroup>Preset colors as a listbox, in a grid that fills its width.
--color-picker-swatch-size sets the chip size, 24px by default, and a
grid-cols-* class fixes the columns. An item is selected while the value is
its color, and shows a check in black or white, whichever contrasts more.
ColorPickerSwatchItem takes value, any color parseColor reads, label,
read out instead of the generated color name, and disabled.
A color chip. It shows the picker's value by default, or color, so it also
works outside a picker, in a table or a legend. Translucent colors sit on a
checkerboard, and an empty value shows a slash. It is hidden from assistive
tech unless you give it an aria-label. Accepts every span prop.
shadcn Buttons for the three actions. Each accepts every Button prop.
ColorPickerEyeDropper samples a color from anywhere on screen with the
browser's EyeDropper API and keeps the current opacity. Where the browser
has no EyeDropper, Firefox and Safari among them, it renders nothing.ColorPickerCopy copies the value in its own format prop or, by default,
the format on screen: what ColorPickerFields shows, else the picker's. It
shows a check for a moment and announces "Copied". ⌘ + C
or Ctrl + C on any control copies the same way, with
no button needed; in a field it does so only when no text is selected, so a
selection still copies as text.ColorPickerClear empties the value, for an optional color.Returns the picker's state and actions inside a ColorPicker, for a custom
readout or action. It throws outside one.
function HexLabel() {
const { color } = useColorPicker()
return color ? <span>{formatColor(color, "hex")}</span> : null
}Pure functions for code that never mounts a picker. They ship in the same
"use client" module as the picker, so a Server Component cannot call them.
const blue = parseColor("#3b82f6") // { space: "rgb", channels: [59, 130, 246], alpha: 1 }
if (blue) formatColor(blue, "oklch") // "oklch(62.31% 0.18801 259.815)"
getContrastRatio("#ffffff", "#3b82f6") // 3.68
getForegroundColor("#3b82f6") // "#ffffff", where the ratio above prefers blackGive the root a name and the value submits with a native form, in format.
An empty picker submits an empty string, so required works, and a disabled
picker submits nothing. form ties the field to a form elsewhere in the page,
and inputRef points at it for form libraries that focus a field on error.
A form reset restores defaultValue, empty unless you passed one; a
controlled picker gets it through onValueChange. A listener that cancels the
reset keeps the value. invalid is for your own validation: pair it with an
error message in aria-describedby, as the form field example on the
Color Picker page does.
<form onSubmit={handleSubmit}>
<FieldLabel htmlFor="brand">Brand color</FieldLabel>
<ColorPicker id="brand" name="brandColor" required>
<ColorPickerInput />
<ColorPickerContent />
</ColorPicker>
</form>On the area, a slider or the wheel:
A step is 1 unit: 1° of hue, 1% of saturation, brightness, lightness or opacity, 1 of red, green or blue, and 0.004 of chroma. The wheel wraps at 360°.
On swatches:
In the fields and ColorPickerInput:
In the popover, Tab and Shift + Tab cycle through the controls, and Esc closes it once no field holds an edit.
sliders. The area is one slider
whose value text names both channels and the color, "Saturation 72%,
Brightness 85%, vibrant blue", since a 2D control has no ARIA role of its
own. The hue slider's value text adds the color name too.listbox with one tab stop. aria-activedescendant points
at the swatch the keyboard is on, and each option is named by its label
or the color name, never by a hex code read digit by digit.aria-labelledby, the trigger is named by your label followed by its
own text, so the value, or what custom children show, stays in the name.dir="ltr" in every text
direction: gradients and channel order do not mirror.forced-color-adjust: none), since the color is the content,
and the thumbs keep an outline.required check focuses it, and it hands focus on to the
trigger, the input or, for an inline panel, the area."use client"
import { useState } from "react"
import {
ColorPicker,
ColorPickerPanel,
} from "@/components/reui/color-picker"
import { Card, CardContent } from "@/components/ui/card"
const SWATCHES = [
"#ef4444",
"#f97316",
"#eab308",
"#22c55e",
"#06b6d4",
"#3b82f6",
"#8b5cf6",
"#ec4899",
]
export function Pattern() {
const [value, setValue] = useState<string | null>("#6366f1")
return (
<Card className="w-full max-w-96">
<CardContent>
{/* `alpha` adds the opacity slider and field; `swatches` adds the
presets row under the default panel. */}
<ColorPicker
value={value}
onValueChange={setValue}
alpha
swatches={SWATCHES}
aria-label="Fill color"
>
<ColorPickerPanel />
</ColorPicker>
</CardContent>
</Card>
)
}
"use client"
import { useState } from "react"
import { Badge } from "@/components/reui/badge"
import {
ColorPicker,
ColorPickerContent,
ColorPickerSwatch,
ColorPickerSwatches,
ColorPickerTrigger,
getForegroundColor,
} from "@/components/reui/color-picker"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { PlusIcon } from 'lucide-react'
const LABEL_COLORS = [
{ value: "#ef4444", label: "Red" },
{ value: "#f97316", label: "Orange" },
{ value: "#eab308", label: "Yellow" },
{ value: "#22c55e", label: "Green" },
{ value: "#14b8a6", label: "Teal" },
{ value: "#3b82f6", label: "Blue" },
{ value: "#8b5cf6", label: "Violet" },
{ value: "#ec4899", label: "Pink" },
{ value: "#64748b", label: "Slate" },
]
export function Pattern() {
const [name, setName] = useState("Needs design")
const [color, setColor] = useState<string | null>("#8b5cf6")
const custom =
!!color && !LABEL_COLORS.some((preset) => preset.value === color)
const ink = color ? getForegroundColor(color) : null
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Edit label</CardTitle>
<CardDescription>Labels group issues across projects.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="label-name">Name</FieldLabel>
<Input
id="label-name"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</Field>
<Field>
<FieldLabel id="label-color-label">Color</FieldLabel>
<ColorPicker value={color} onValueChange={setColor}>
<div className="flex items-center gap-3">
<ColorPickerSwatches
colors={LABEL_COLORS}
shape="circle"
aria-labelledby="label-color-label"
className="flex-1"
/>
{/* The plus opens the full picker; once a custom color is
set, it shows that color instead. */}
<ColorPickerTrigger size="icon-sm" aria-label="Custom color">
{custom ? (
<ColorPickerSwatch shape="circle" />
) : (
<PlusIcon aria-hidden="true" />
)}
</ColorPickerTrigger>
<ColorPickerContent align="end" />
</div>
</ColorPicker>
</Field>
<div className="flex items-center gap-3">
<span className="text-muted-foreground text-sm">Preview</span>
<Badge
style={{
backgroundColor: color ?? undefined,
color: ink ?? undefined,
}}
>
{name.trim() || "Untitled"}
</Badge>
</div>
</FieldGroup>
</CardContent>
</Card>
)
}
"use client"
import { useState } from "react"
import type { CSSProperties } from "react"
import { Badge } from "@/components/reui/badge"
import {
ColorPicker,
ColorPickerChannelInput,
ColorPickerCopy,
ColorPickerSlider,
getForegroundColor,
isColorInSrgb,
parseColor,
} from "@/components/reui/color-picker"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/components/ui/input-group"
import { Label } from "@/components/ui/label"
const CHANNELS = [
{ channel: "lightness", short: "L" },
{ channel: "chroma", short: "C" },
{ channel: "hue", short: "H" },
] as const
export function Pattern() {
/* Tailwind's blue-500, a P3 color: its chroma runs past sRGB. */
const [primary, setPrimary] = useState<string | null>(
"oklch(62.3% 0.214 259.815)"
)
const color = parseColor(primary)
const foreground = color ? getForegroundColor(color) : null
/* The preview re-points the theme tokens themselves, so the Button and the
Badge below render exactly as they would with this --primary. */
const theme =
primary && foreground
? ({
"--primary": primary,
"--primary-foreground": foreground,
} as CSSProperties)
: undefined
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Primary color</CardTitle>
<CardDescription>
Tune the --primary token in OKLCH, the format shadcn and Tailwind v4
themes use.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-5">
<ColorPicker
value={primary}
onValueChange={setPrimary}
format="oklch"
aria-label="Primary color"
>
<div className="grid gap-3">
{CHANNELS.map(({ channel, short }) => (
<div
key={channel}
className="grid grid-cols-[1rem_1fr_4.5rem] items-center gap-3"
>
<span
aria-hidden="true"
className="text-muted-foreground text-xs font-medium"
>
{short}
</span>
<ColorPickerSlider channel={channel} colorSpace="oklch" />
<InputGroup>
<ColorPickerChannelInput
channel={channel}
colorSpace="oklch"
/>
</InputGroup>
</div>
))}
</div>
<InputGroup>
<InputGroupInput
readOnly
value={`--primary: ${primary ?? "none"};`}
aria-label="CSS variable"
/>
<InputGroupAddon align="inline-end">
<ColorPickerCopy size="icon-xs" />
</InputGroupAddon>
</InputGroup>
</ColorPicker>
<div className="flex flex-wrap items-center gap-3" style={theme}>
{/* Each fills with --primary and draws on --primary-foreground,
so all three follow the picked color. */}
<Button>Save changes</Button>
<Badge>New</Badge>
<div className="flex items-center gap-2">
<Checkbox id="primary-notify" defaultChecked />
<Label htmlFor="primary-notify">Notify team</Label>
</div>
{color && !isColorInSrgb(color) && (
<Badge variant="warning-light" className="ms-auto">
Outside sRGB
</Badge>
)}
</div>
</div>
</CardContent>
</Card>
)
}