Color picker
Free · MITReact Native · ExpoSwatches with a selection ring that springs from swatch to swatch while the chosen swatch shrinks inside it, plus an optional pad: drag the thumb over saturation and brightness, slide the hue bar, and the before and after preview follows live.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/color-picker.jsonThen run npx expo install --fix so react-native-gesture-handler, react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/color-picker.
Usage
import { useState } from "react";
import { ColorPicker } from "@/components/goodui-native/color-picker/color-picker";
export function CalendarColor() {
const [color, setColor] = useState("#2F6FDB");
return (
<ColorPicker
value={color}
onValueChange={setColor}
swatches={[
{ value: "#2F6FDB", label: "Blue" },
{ value: "#1F9D6B", label: "Green" },
{ value: "#E0A21B", label: "Amber" },
{ value: "#E5484D", label: "Red" },
{ value: "#8E4EC6", label: "Purple" },
]}
/>
);
}When to use
- Tagging things with a color: calendars, labels, folders, habits, wallet cards.
- Letting people pick a brand or theme color, with the pad for exact colors.
- Choosing the app's own accent from a fixed set of names: a ChipGroup from chip or segmented-control reads better.
- Choosing among products that happen to differ by color (a phone in three finishes): use a ChipGroup with labels, color alone is not enough.
Examples
Pad always open, square swatches
<ColorPicker custom="inline" shape="square" size="sm" swatches={brandColors} value={brand} onValueChange={setBrand} />Swatches only
<ColorPicker custom="none" swatches={["#F2F2F2", "#1C1C1E", "#C7B299"]} defaultValue="#1C1C1E" accessibilityLabel="Finish" />API reference
| Prop | Type | Default |
|---|---|---|
| swatches* The swatches, as "#RRGGBB" strings or `{ value, label }` with a name for screen readers. | ColorSwatch[] | — |
| value Selected color as "#RRGGBB" (controlled). | string | — |
| defaultValue Starting color when uncontrolled. Defaults to the first swatch. | string | — |
| onValueChange Called with "#RRGGBB" on a swatch tap, and when the finger lifts from the pad or the hue bar. | (color: string) => void | — |
| custom The custom color pad (saturation and brightness, a hue bar and a preview). `toggle` adds a custom swatch that opens it, `inline` always shows it, `none` leaves swatches only. | "none" | "toggle" | "inline" | "toggle" |
| size Swatch diameter 32 (`sm`) or 40 pt (`md`), always with a 44 pt touch target. | "sm" | "md" | "md" |
| shape Round swatches, or rounded squares. | "circle" | "square" | "circle" |
| haptic `false` silences the select haptic. | boolean | true |
| accessibilityLabel Names the swatch group for screen readers. Same as `labels.color`. | string | — |
| labels Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. | Partial<ColorPickerLabels> | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- The ring travels between swatches: the edges facing the new swatch lead on the snappy spring and the others follow on smooth, so it stretches across and settles, across rows too.
- The selected swatch shrinks to 78 % on the bouncy spring, opening a gap inside the ring; the old one grows back on snappy.
- The pad thumb and the hue thumb follow the finger 1:1 from touch down; the pad, the thumb fill and the preview recolor on the UI thread while dragging. A swatch tap moves the thumbs there on the smooth spring.
- The pad opened from the custom swatch drops in on the smooth spring.
- Reduced motion: the ring jumps, swatch scaling is a short timing, the thumbs jump to a swatch's color, and the pad appears in place.
- Haptic: select when the color changes from a swatch, a pad or hue release, or a key
- Haptic: haptic={false} silences it
- Dragging updates shared values only; React renders once when the finger lifts, which is also when onValueChange fires.
- Gradients are react-native-svg linear gradients over a view whose hue is animated, so the pad costs one view and one svg.
Accessibility
- Swatches are radios in a radiogroup named by accessibilityLabel (default "Color"); give swatches a label so they are read as names, otherwise the hex code is read.
- The custom swatch is a button ("Custom color", with the current custom hex) that reports expanded while the pad is open.
- The pad is adjustable: increment and decrement change brightness, and "More saturated" and "Less saturated" actions change saturation, in 5 % steps; its value reads "Saturation 80 percent, brightness 60 percent".
- The hue bar is adjustable in 5° steps with a value in degrees.
- On the web the pad and the hue bar are focusable sliders with aria-valuenow, min, max and valuetext: Left and Right change saturation, Up and Down brightness on the pad; arrows change the hue on the bar.
- Swatch touch areas are 44 pt whatever the swatch size. The preview is read as "Was #…, now #…".
Notes for AI
- Colors are "#RRGGBB" strings (3-digit and lower case are accepted and normalized to upper case). onValueChange gets upper case "#RRGGBB".
- onValueChange fires when a drag ends, not during it. Use the value for saving; the live preview is internal.
- A value not among the swatches selects the custom swatch (custom="toggle") and shows inside it.
- Swatch colors are content: pass your palette. The component draws its own chrome from theme tokens.
Related
Filter, choice and input chips: selecting one fills it with the accent while a check draws itself in, removing one shrinks it away, and a ChipGroup in single mode moves one elastic highlight from chip to chip.
Single and range sliders whose thumbs follow the finger 1:1 and settle onto the nearest step on release, with a value bubble that grows out of the thumb while dragging and optional step ticks that tick.
Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.