Color picker

Free · MITReact Native · Expo

Swatches 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.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/color-picker.json

Then 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

Use it for
  • 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.
Reach for something else
  • 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

PropTypeDefault
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.
booleantrue
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.