ColorPicker
Pick any color. A trigger shows the current color; it opens a panel with a saturation and brightness area, a hue bar and a hex field. The value is always a 6-digit hex string.
Xem trước
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" />Cách dùng
Khi nào dùng
- Any color is a valid answer: a brand or accent color, a label color, a chart series color.
- A color the user wants to see change the page while they drag.
Khi nào không dùng
- A few named colors. Use RadioGroup with swatches.
- A color with transparency. It picks opaque colors only.
- A hex value typed from a design file with nothing else to adjust. Use Input.
Biến thể
variant
input shows swatch and hex like a field, for forms. swatch is a square swatch-only button, for toolbars and headers.
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" variant="swatch" />size
Trigger height from size-element-*, same as Input. The panel does not change with size.
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" size="sm" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" size="lg" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" size="xl" />align
Panel alignment against the trigger, same as Popover. Ignored below md, where the panel is a bottom Sheet.
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" align="center" />
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" align="end" />Ví dụ
Basic
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" />Named trigger
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Accent color" triggerLabel="Accent" />Live preview, save on release
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Accent color" defaultValue="#7c5cff" onValueChange={previewColor} onValueCommit={saveColor} />Swatch trigger with presets
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Label color" variant="swatch" size="sm" swatches={["#d93a3a", "#e0a020", "#2a9d5c", "#2f7de1", "#7c5cff"]} defaultValue="#2a9d5c" />Disabled
import { ColorPicker } from "@/components/ui/color-picker"
<ColorPicker aria-label="Brand color" defaultValue="#7c5cff" disabled />Nên và không nên
Nên
- Use it when any color is a valid answer: brand or accent color, label color, chart series color.
- Save in onValueCommit. Use onValueChange only for live preview.
- Give it an aria-label that says what the color is for.
- Use variant swatch in tight spaces like a header or toolbar, variant input in forms.
- Keep contrast fixes, history like recently used colors, and saving to storage in the app. The component only picks a hex.
- Pass translated aria-label, areaLabel, hueLabel, saturationLabel, brightnessLabel and hexLabel on non-English pages.
Không nên
- Don't use it to choose between a few named colors. Use RadioGroup or SegmentedControl with color swatches.
- Don't add a color library for it. Hex, RGB and HSV conversion is a few lines inside the component.
- Don't use the native color input inside it; it looks different on every platform.
- Don't change the theme on every onValueChange without throttling to one update per frame.
- Don't put it in a Dialog footer or a Menu; the panel needs its own space.
Props
| Tên | Kiểu | Giá trị | Mặc định | Mô tả |
|---|---|---|---|---|
| value | string | - | - | Controlled color, lowercase 6-digit hex like "#7c5cff". Pair with onValueChange. |
| defaultValue | string | - | #111111 | Starting color when uncontrolled. |
| onValueChange | function | - | - | (value: string) => void. Fires on every change while dragging, on each key step and on a valid hex commit. Can fire many times per second; the caller decides whether to throttle. |
| onValueCommit | function | - | - | (value: string) => void. Fires once when the pointer is released, after a key step, on a valid hex commit, and when the panel closes with a changed color. Save or send the value here, not in onValueChange. |
| open | boolean | - | - | Controlled open state of the panel. Pair with onOpenChange. |
| defaultOpen | boolean | - | false | Starting open state when uncontrolled. |
| onOpenChange | function | - | - | (open: boolean) => void. |
| variant | enum | input | swatch | input | input shows swatch and hex like a field, for forms. swatch is a square swatch-only button, for toolbars and headers. |
| triggerLabel | string | - | - | Visible text in place of the hex on an input trigger, such as Accent. The swatch stays visible and the accessible name still includes the current hex. Ignored on a swatch trigger. |
| size | enum | sm | md | lg | xl | md | Trigger height from size-element-*, same as Input. The panel does not change with size. |
| swatches | string | - | - | Optional string[] of hex values shown as presets under the hex field, e.g. ["#d93a3a", "#2a9d5c"]. Leave empty to hide the row. (The spec schema has no array type, so it is declared as string.) |
| showHexField | boolean | - | true | Show the hex field. It is the main way for screen reader users to pick a color, so turn it off only when another text entry for the same value sits right next to the picker. |
| align | enum | start | center | end | start | Panel alignment against the trigger, same as Popover. Ignored below md, where the panel is a bottom Sheet. |
| areaLabel | string | - | Saturation and brightness | Accessible name of the area thumb. Pass a translated string in other languages. |
| hueLabel | string | - | Hue | Accessible name of the hue thumb. |
| saturationLabel | string | - | saturation | Axis word read in the area's aria-valuetext after the hex, e.g. "#7c5cff, saturation 64%". |
| brightnessLabel | string | - | brightness | Axis word read in the area's aria-valuetext when brightness changed, e.g. "#7c5cff, brightness 40%". |
| hexLabel | string | - | Hex | Visible label of the hex field. |
| hexPlaceholder | string | - | - | Placeholder of the hex field. |
| hexError | string | - | - | Shown under the hex field, with role="alert", while the typed text is not a color. Pass it translated. |
| title | string | - | - | A visible heading at the top of the panel. It also names the Sheet below md; without it the Sheet is named by aria-label, visually hidden. |
| children | node | - | - | App-specific content under the presets, such as a reset link, a help line or export buttons. The component itself holds no copy beyond its label props. |
| disabled | boolean | - | false | Trigger can't be focused or opened. |
| invalid | boolean | - | false | Marks the trigger invalid in a form (aria-invalid). The hex field also shows invalid on its own while the typed text is not a color. |
| name | string | - | - | Form field name. Renders a hidden input with the committed hex. |
Cấu tạo
| Phần | Mô tả |
|---|---|
| root | ColorPicker. Holds the value and the open state; renders nothing itself. Built on Popover. |
| trigger | ColorPickerTrigger: the button that opens the panel. variant input looks like Input (same fill, border, sizes) with the swatch at the start and the hex in font-family-mono after it. variant swatch is a square button of size-element-* with only the swatch inside, for toolbars and headers. triggerLabel replaces the visible hex with a UI label in font-family-sans, keeping the hex in its accessible name. |
| swatch | A square of the current color, 16px in sm, md and lg, 20px in xl, radius-inner, 1px color-border-strong so white and near-page colors still show their edge. Its fill is the value itself, not a token. Disabled, it dims to opacity-disabled, since no token can grey out the value. |
| content | ColorPickerContent: from md up, PopoverContent with the same surface, border, radius, shadow and padding as Popover, 288px wide (never wider than the screen minus spacing-sm each side). Below md, a Sheet with side="bottom" holding the same children, full width minus the Sheet padding. Children stack with gap spacing-md. A disabled picker opened from code dims the whole panel to opacity-disabled (the panel carries data-disabled itself, since it is portaled). |
| area | ColorPickerArea: the saturation (x) and brightness (y) field for the current hue. Full content width, aspect ratio 8:5 in both Popover and Sheet (256 by 160 at 288px), radius-inner, touch-action none so a drag never scrolls the page or swipes the Sheet closed. Its gradient is computed from the hue, not a token. |
| areaThumb | 16px circle that marks the current saturation and brightness: solid color-background-raised fill, 1px color-border-strong edge, shadow-low. Raised fill and strong border are a light and dark pair in both modes, so the thumb shows on any color. The point is its center; it may overhang the area edge by half its width. |
| hue | ColorPickerHue: horizontal bar of all hues, full content width, 12px tall, radius-full, touch-action none. Its gradient is computed, not a token. |
| hueThumb | Same 16px thumb as areaThumb, vertically centered on the bar. |
| hexField | ColorPickerInput: Input, font-family-mono, the swatch as its leading slot, maxLength 7. Size sm in the Popover, lg in the Sheet. Commits on Enter and on blur; Escape reverts to the last valid value. |
| swatches | ColorPickerSwatches: optional row of preset colors under the hex field, gap spacing-xs, wraps. Each preset is a radius-inner button with the same border as swatch: 20px in the Popover, size-element-lg in the Sheet. The one equal to the value is selected. |
| hitArea | Transparent ::before centered on the trigger, each thumb and each preset, never smaller than size-target-min. |
Khả năng truy cập
Role: button
| Phím | Hành động |
|---|---|
| Enter / Space | On the trigger: open the panel. Focus moves to the area thumb. |
| Escape | Close the panel (Popover or Sheet). Focus returns to the trigger. Commits the color if it changed. |
| Tab / Shift+Tab | Move between area thumb, hue thumb, hex field and swatches inside the panel. |
| ArrowLeft / ArrowRight | On the area: saturation down or up 1%. On the hue bar: hue down or up 1 degree. |
| ArrowDown / ArrowUp | On the area: brightness down or up 1%. On the hue bar: same as Left and Right. |
| Shift+Arrow | Step 10% on the area, 10 degrees on the hue bar. |
| PageUp / PageDown | Same as Shift+Arrow. |
| Home / End | On the area: saturation 0% or 100%. On the hue bar: 0 or 359 degrees. |
| Enter | In the hex field: commit the typed value if valid. If not valid, keep the field invalid and the color unchanged. |
- aria-label (or aria-labelledby) is required: it says what the color is for, e.g. "Accent color". The trigger's accessible name is that label plus the current hex, e.g. "Accent color, #7c5cff".
- The area thumb and the hue thumb are each role="slider", named by areaLabel and hueLabel. A slider has only one aria-valuenow: on the area it follows the axis that last changed (saturation for Left/Right, brightness for Up/Down), so every key press is read back. aria-valuetext is the hex plus that axis, built from the label props, e.g. "#7c5cff, saturation 64%" or, on a Vietnamese page, "#7c5cff, độ bão hòa 64%". On the hue bar it is the hex plus degrees, e.g. "#7c5cff, 252°".
- Screen readers on phones only offer increase and decrease, so they reach one axis of the area. The hex field is the main way for screen reader users to pick a color; the area and hue bar are for pointer and keyboard. Never hide the hex field when there is no other text entry nearby.
- The hex field is an Input labeled by hexLabel. It accepts 3 or 6 digits, with or without #, any case, and writes back lowercase 6-digit hex on commit.
- Every thumb and swatch keeps a ::before hit area of at least size-target-min (24px mouse, 44px touch).
- No alpha channel in v1. No color names are announced: the component ships no color library, so hex is the only spoken value.
- Hue keeps its own state while saturation or brightness is 0, so moving back up from black or gray does not jump the hue to red.
- Below md the panel opens as a Sheet from the bottom. The Sheet title is the picker's aria-label (visually hidden is fine). Closing it by swipe, scrim or Escape commits like Escape and returns focus to the trigger.
- Every built-in string (areaLabel, hueLabel, saturationLabel, brightnessLabel, hexLabel) is a prop so apps can translate it. The component holds no copy of its own beyond those English defaults.
Cài đặt
npx shadcn add https://werewolf-ui.vercel.app/r/color-picker.jsonLệnh này thêm luôn styles/werewolf.css, bộ token mà mọi component dùng, và các component khác mà nó cần. Import file đó một lần trong CSS gốc, ngay sau @import "tailwindcss", với đường dẫn tính từ file CSS đó (từ app/globals.css là @import "../styles/werewolf.css").