Color Picker React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React color picker with a saturation and brightness square, hue and opacity sliders, HEX / RGB / HSL inputs, swatches and an eyedropper.
Click the field (or the round swatch button) to open a panel under it: drag in the square to set saturation (left to right) and brightness (bottom to top), drag the hue bar, and with alpha the opacity bar; the thumb grows a little while you drag and the value updates live, once per frame. Below them the HEX / RGB / HSL switch changes which fields show (the output keeps its format); typed values apply on Enter or when the field loses focus, text that is not a color turns the field red and goes back to the last good color, ArrowUp / ArrowDown in a number field step by 1 (Shift by 10), and a 3-digit hex expands ("F60" is #FF6600). Tap a preset swatch to use it: a ring slides to it. In Chrome and Edge a pipette button picks any pixel on screen. Keyboard: Enter, Space or ArrowDown on the trigger opens the panel with focus in the square; in the square ArrowLeft / ArrowRight change saturation and ArrowUp / ArrowDown brightness by 1% (Shift 10%), PageUp / PageDown step brightness by 10%, Home / End go to no or full saturation; the hue and opacity bars take the arrow keys, Home and End; the swatches are a radio group moved with the arrow keys; Escape closes the panel and returns focus to the trigger. Clicking the label focuses the trigger. On a phone the panel opens in a bottom sheet, full width, with a taller square, 28px thumbs and larger swatches; the changes are a draft until Done, and dragging the sheet away keeps the old color. Disabled and read-only pickers never open. ColorPickerPanel (the panel alone, with the same value, format, alpha, swatches and labels props) is exported for your own popovers and pages, with the helpers parseColor (hex, rgb(), hsl() and the 16 basic CSS names, no canvas), formatColor, hsvaToRgba, rgbaToHsva and COLOR_PICKER_SWATCHES.
Color Picker is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add color-picker, together with Button, Drawer, Field, Segmented Control, useControllableState and 3 more and the npm packages @radix-ui/react-popover, @radix-ui/react-slider, motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add color-pickerColor Picker is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import ColorPicker from @/components/ui/color-picker.
import { useState } from "react"
import { ColorPicker } from "@/components/ui/color-picker"
export function StoreColor() {
const [color, setColor] = useState("#FC4C01")
return (
<ColorPicker
label="Primary color"
description="The store's buttons and links use this color."
value={color}
onValueChange={setColor}
onValueCommit={(saved) => console.log("saved", saved)}
name="primary_color"
placeholder="Choose a brand color"
sheetTitle="Brand color"
labels={{ swatches: "Brand presets", done: "Apply" }}
/>
)
}Props
labeldefault""ReactNodeThe field label. The trigger reads it with the value ("Primary color, #FC4C01"); plain text also titles the phone sheet.
descriptiondefault""ReactNodeHelper text under the field, linked with aria-describedby.
errordefault""ReactNodeReplaces the description with an animated error and marks the field invalid.
requireddefaultfalsebooleanThe red mark after the label and aria-required on the trigger.
invaliddefaultfalsebooleanThe invalid look and aria-invalid without a message.
disableddefaultfalsebooleanDims the field and never opens the panel; the hidden input is not submitted.
readOnlydefaultfalsebooleanThe color stays visible and the trigger focusable, but the panel never opens (inline: nothing can change).
defaultValuedefault""stringThe starting color while uncontrolled, any CSS color we can read ("#FC4C01", "rgb(252 76 1)", "hsl(18 99% 50%)", "red"). A form reset returns to it.
valuestringThe color, controlled, as a CSS color in format ("" is empty). A value in another format is shown as given and re-emitted in format on the next change.
onValueChange(value: string) => voidCalled with the new color in format: live while dragging (at most once per frame), after a typed value, a swatch or the eyedropper. On phones only when Done is pressed.
onValueCommit(value: string) => voidCalled when a change is final: pointer up after a drag, Enter or blur in a field, a swatch, an eyedropper pick, a key in the square, the sheet's Done. Save here.
formatdefault"hex""hex" | "rgb" | "hsl"The output: "#FC4C01", "rgb(252, 76, 1)" or "hsl(18, 99%, 50%)". Also the fields the panel shows first; the panel's switch only changes the fields. The exported parseColor(text) and formatColor(hsva, format, alpha) do the same conversions for your own code.
alphadefaultfalsebooleanAn opacity slider and an opacity field. Hex becomes #RRGGBBAA below 100% opacity; rgb and hsl always become rgba() / hsla(). Off, colors are always opaque.
swatchesdefault"["#0A0A0A", "#525252", "#A3A3A3", "#FFFFFF", "#EF4444", "#F97316", "#F59E0B", "#EAB308", "#84CC16", "#22C55E", "#14B8A6", "#0EA5E9", "#3B82F6", "#6366F1", "#8B5CF6", "#EC4899"]"string[]Preset colors in a grid of 8 per row. Defaults to the exported COLOR_PICKER_SWATCHES: 16 colors (neutrals, red, orange, amber, yellow, lime, green, teal, sky, blue, indigo, violet, pink), easy to extend. [] hides the row.
showInputsdefaulttruebooleanThe format switch, the text fields and the eyedropper under the sliders.
showFormatSwitchdefaulttruebooleanThe HEX / RGB / HSL segmented control above the fields.
showEyeDropperdefaulttruebooleanThe pipette button. It only renders where the browser has window.EyeDropper (Chrome and Edge on desktop), so it never appears broken.
inlinedefaultfalsebooleanRenders the panel in place (settings pages): no trigger, no popover, no sheet. Changes apply at once.
triggerdefault"field""field" | "swatch"A field box with a color chip and the value, or a round swatch button sized like an icon button (toolbars, compact forms).
placeholderdefault"Pick a color"stringThe trigger's text while empty (with a checkerboard chip).
labelsPartial<{ area; saturation; brightness; hue; alpha; hex; red; green; blue; hueInput; lightness; format; eyeDropper; swatches; done; trigger: string }>Every spoken and visible string, merged over English defaults ("Saturation and brightness", "Hue", "Opacity", "Done"...). trigger names the trigger when there is no label ("Color, #FC4C01").
sizedefault"md""sm" | "md" | "lg"Trigger heights of 32, 40 and 48px (md is 44px on touch screens); the swatch button is square at the same sizes.
variantdefault"soft""soft" | "outline" | "underline" | "ghost"The field box look (trigger field): a tinted fill, a border, a bottom line, or nothing until hover.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corner radius of the field, scaled per size like the button; full also makes the color chip round. Ignored by underline.
namedefault""stringRenders a hidden input with this name and the color text, for native forms.
defaultOpendefaultfalsebooleanStarts with the panel (or the sheet) open. Pass open and onOpenChange to control it.
openbooleanWhether the panel or sheet is open, controlled. It never opens while disabled or readOnly.
onOpenChange(open: boolean) => voidCalled when the panel or sheet opens or closes.
responsivedefaulttruebooleanBelow 768px the panel opens in a bottom sheet with a Done button that commits the draft. Off, phones get the popover too.
sheetTitledefault"Pick a color"stringThe phone sheet's title (and the popover's accessible name) when the label is not plain text or missing.
sidedefault"bottom""top" | "right" | "bottom" | "left"Where the panel opens; it flips when there is no room.
aligndefault"start""start" | "center" | "end"How the panel lines up with the trigger.
containerHTMLElement | nullPortal target of the panel and the sheet; defaults to document.body.
fieldPropsPartial<FieldProps>Anything else for the Field wrapper: optional, labelAction, orientation, labelWidth, hideLabel.
idstringThe trigger's id (the panel's with inline); generated when left out.
classNamesPartial<Record<"field" | "label" | "description" | "error" | "counter" | "trigger" | "swatch" | "value" | "chevron" | "content" | "panel" | "area" | "thumb" | "hue" | "alpha" | "preview" | "inputs" | "format" | "input" | "eyedropper" | "swatches" | "swatchOption" | "footer", string>>Extra classes for the inner parts. content is the popover or the sheet, thumb the square's thumb, input every text field in the panel.
classNamestringExtra classes for the outermost element: the Field when there is a label or message, else the trigger (or the inline panel).
refRef<HTMLButtonElement> (a useRef object or an (el) => void callback)The trigger button. Other props (onBlur, autoFocus, aria-*, data-*) land on it too.
Parts
ColorPickerPanel
componentThe picker panel on its own (square, hue and opacity bars, fields, format switch, swatches, eyedropper) for your own popover, sheet or settings page. Takes value / defaultValue, onValueChange, onValueCommit, format, alpha, swatches, showInputs, showFormatSwitch, showEyeDropper, labels, disabled, readOnly, autoFocus and classNames, the same as ColorPicker.
parseColor
functionReads hex (3, 4, 6 or 8 digits), rgb() / rgba(), hsl() / hsla() and the 16 basic CSS color names into an Hsva, without a canvas. Returns null for text that is not a color. Signature: (text: string | null | undefined) => Hsva | null.
formatColor
functionWrites an Hsva as "#FC4C01", "rgb(252, 76, 1)" or "hsl(18, 99%, 50%)", with the alpha channel when alpha is true. Signature: (hsva: Hsva, format?: ColorFormat, alpha?: boolean) => string.
hsvaToRgba
functionConverts hue / saturation / value / alpha to red / green / blue / alpha (0-255 channels). Signature: (hsva: Hsva) => Rgba.
rgbaToHsva
functionConverts red / green / blue / alpha (0-255 channels) to hue / saturation / value / alpha. Signature: (rgba: Rgba) => Hsva.
COLOR_PICKER_SWATCHES
constantThe 16 default preset colors as hex strings: neutrals, then red through pink. Spread it to add your brand colors.
ColorPickerPanelProps
typeProps of ColorPickerPanel.
ColorFormat
type"hex" | "rgb" | "hsl", the output format and the fields the panel shows first.
Hsva
type{ h, s, v, a }: hue 0-360, saturation and value 0-100, alpha 0-1. The internal color model.
Examples
Inline panel
inline renders the panel in a settings card; changes apply at once.
Opacity
alpha adds the opacity slider and field; format rgb emits rgba().
Swatch triggers
trigger="swatch" round buttons in an editor toolbar.
Formats
One color emitted as hex, rgb and hsl, converted with parseColor / formatColor.
Eyedropper
The pipette picks any pixel on screen where the browser supports it.
Brand palette
Custom swatches only, without the fields.
Sizes and states
sm / md / lg, empty, error and disabled.
Dependencies
Files
- components/ui/color-picker.tsx
FAQ
Is the React Color Picker component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Color Picker?
Run npx wingo-ui@latest add color-picker. It also adds the Wingo UI items it builds on (Button, Drawer, Field, Segmented Control, useControllableState and 3 more) and the npm packages @radix-ui/react-popover, @radix-ui/react-slider, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/color-picker.
How does Color Picker behave on phones?
Below 768px the panel opens in a bottom sheet with a Done button that commits the draft. Off, phones get the popover too. The responsive prop controls this and is on by default.
See also
Used in
More React input components
Articles using this component
- React Color Picker: HEX, RGB, HSL, Eyedropper and OKLCH
Component guides9 min read
