Skip to main content
0.77.1
View Zag.js on Github
Join the Discord server

Color Picker

The color picker is an input widget used to select a color value from a predefined list or a color area.

This component builds on top of the native <input type=color> experience and provides a more customizable and consistent user experience.

Properties

Features

  • Support for custom color area
  • Support for RGBA, HSLA, HEX, and HSBA formats
  • Support for channel inputs and sliders
  • Support for mouse, touch, and keyboard interactions
  • Support for form submission and reset events
  • Support for named css colors

Installation

To use the color picker machine in your project, run the following command in your command line:

npm install @zag-js/color-picker @zag-js/react # or yarn add @zag-js/color-picker @zag-js/react

This command will install the framework agnostic color picker logic and the reactive utilities for your framework of choice.

Anatomy

To set up the color picker correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

First, import the color picker package into your project

import * as colorPicker from "@zag-js/color-picker"

The color picker package exports these functions:

  • machine — The state machine logic for the color picker widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.
  • parse - The function that parses a color string to an Color object.

Next, import the required hooks and functions for your framework and use the color picker machine in your project 🔥

import * as colorPicker from "@zag-js/color-picker" import { normalizeProps, useMachine } from "@zag-js/react" import { useId } from "react" function ColorPicker() { const [state, send] = useMachine( colorPicker.machine({ id: useId(), value: colorPicker.parse("hsl(0, 100%, 50%)"), }), ) const api = colorPicker.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Select Color: {api.valueAsString}</label> <input {...api.getHiddenInputProps()} /> <div {...api.getControlProps()}> <button {...api.getTriggerProps()}> <div {...api.getTransparencyGridProps({ size: "10px" })} /> <div {...api.getSwatchProps({ value: api.value })} /> </button> <input {...api.getChannelInputProps({ channel: "hex" })} /> <input {...api.getChannelInputProps({ channel: "alpha" })} /> </div> <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> <div {...api.getAreaProps()}> <div {...api.getAreaBackgroundProps()} /> <div {...api.getAreaThumbProps()} /> </div> <div {...api.getChannelSliderProps({ channel: "hue" })}> <div {...api.getChannelSliderTrackProps({ channel: "hue" })} /> <div {...api.getChannelSliderThumbProps({ channel: "hue" })} /> </div> <div {...api.getChannelSliderProps({ channel: "alpha" })}> <div {...api.getTransparencyGridProps({ size: "12px" })} /> <div {...api.getChannelSliderTrackProps({ channel: "alpha" })} /> <div {...api.getChannelSliderThumbProps({ channel: "alpha" })} /> </div> </div> </div> </div> ) }

Setting the initial value

To set the initial value of the color picker, use the value context property.

const [current, send] = useMachine( colorPicker.machine({ value: colorPicker.parse("#ff0000"), }), )

Listening for change events

When the user selects a color using the color picker, the onValueChange and onValueChangeEnd events will be fired.

  • onValueChange — Fires in sync as the user selects a color
  • onValueChangeEnd — Fires when the user stops selecting a color (useful for debounced updates)
const [current, send] = useMachine( colorPicker.machine({ onValueChange: (details) => { // details => { value: Color, valueAsString: string } }, onValueChangeEnd: (details) => { // details => { value: Color, valueAsString: string } }, }), )

When using the onValueChange method in React.js, you might need to use the flushSync method from react-dom to ensure the value is updated in sync

Using a custom color format

By default, the color picker's output format is rgba. You can change this format to either hsla or hsba by using the format context property.

When this property is set, the value and valueAsString properties of the onValueChange event will be updated to reflect the new format.

const [current, send] = useMachine( colorPicker.machine({ format: "hsla", onValueChange: (details) => { // details => { value: HSLAColor, valueAsString: string } }, }), )

Showing color presets

Adding color presets in form of swatches can help users pick colors faster. To support this, use the getSwatchTriggerProps(...) and getSwatchProps(...) to get the props needed to show the swatches buttons.

const ColorPicker = () => { const [state, send] = useMachine( colorPicker.machine({ id: useId(), value: colorPicker.parse("hsl(0, 100%, 50%)"), }), ) const api = colorPicker.connect(state, send, normalizeProps) const presets = ["#ff0000", "#00ff00", "#0000ff"] return ( <div {...api.getRootProps()}> {/* ... */} <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> <div {...api.getSwatchGroupProps()}> {presets.map((preset) => ( <button key={preset} {...api.getSwatchTriggerProps({ value: preset })} > <div style={{ position: "relative" }}> <div {...api.getTransparencyGridProps({ size: "4px" })} /> <div {...api.getSwatchProps({ value: preset })} /> </div> </button> ))} </div> </div> </div> </div> ) }

Disabling the color picker

To disable user interactions with the color picker, set the disabled context property to true.

const [current, send] = useMachine( colorPicker.machine({ disabled: true, }), )

Controlling the open and closed state

To control the open and closed state of the color picker, use the open and onOpenChange context properties.

const [current, send] = useMachine( colorPicker.machine({ open: true, onOpenChange: (details) => { // details => { open: boolean } }, }), )

You can also leverage the api.setOpen(...) method to control the open and closed state of the color picker.

Controlling individual color channel

In some cases, you may want to allow users to control the values of each color channel individually. You can do this using an input element or a slider element, or both.

To support this, use the getChannelInputProps(...) to show the channel inputs.

Note: Make sure you only render the channel inputs that match the format of the color picker.

const ColorPicker = () => { const [state, send] = useMachine( colorPicker.machine({ id: useId(), value: colorPicker.parse("hsl(0, 100%, 50%)"), }), ) const api = colorPicker.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> {/* ... */} <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> {api.format === "rgba" && ( <div> <div> <span>R</span> <input {...api.getChannelInputProps({ channel: "red" })} /> </div> <div> <span>G</span> <input {...api.getChannelInputProps({ channel: "green" })} /> </div> <div> <span>B</span> <input {...api.getChannelInputProps({ channel: "blue" })} /> </div> <div> <span>A</span> <input {...api.getChannelInputProps({ channel: "alpha" })} /> </div> </div> )} </div> </div> </div> ) }

Showing a color preview

To display the value of a color, use the getSwatchProps(...) and pass the color value. To show the current color value, use the api.value

const ColorPicker = () => { const [state, send] = useMachine( colorPicker.machine({ id: useId(), value: colorPicker.parse("hsl(0, 100%, 50%)"), }), ) const api = colorPicker.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> <div> <div {...api.getTransparencyGridProps({ size: "4px" })} /> <div {...api.getSwatchProps({ value: api.value })} /> </div> {/* ... */} </div> ) }

You can pass respectAlpha: false to show the color value without the alpha channel

Adding an eyedropper

The eye dropper tool is a native browser feature that allows a user pick a color from a current page's canvas. To support this, use the getEyeDropperTriggerProps(...).

Note: The eye dropper tool only works in Chrome and Edge browsers

const ColorPicker = () => { const [state, send] = useMachine( colorPicker.machine({ id: useId(), value: colorPicker.parse("hsl(0, 100%, 50%)"), }), ) const api = colorPicker.connect(state, send, normalizeProps) return ( <div {...api.getRootProps()}> {/* ... */} <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> <button {...api.getEyeDropperTriggerProps()}> <EyeDropIcon /> </button> </div> </div> </div> ) }

Usage within forms

To use the color picker within a form, add the name context property to the machine and render the visually hidden input using the hiddenInputProps.

const [state, send] = useMachine( colorPicker.machine({ name: "color-preference", }), )

Styling guide

Each color picker part has a data-part attribute added to them to help you identify and style them easily.

Open and closed state

When the color picker is open or closed, the data-state attribute is added to the trigger, content, control parts.

[data-part="control"][data-state="open|closed"] { /* styles for control open or state */ } [data-part="trigger"][data-state="open|closed"] { /* styles for control open or state */ } [data-part="content"][data-state="open|closed"] { /* styles for control open or state */ }

Focused State

When the color picker is focused, the data-focus attribute is added to the control and label parts.

[data-part="control"][data-focus] { /* styles for control focus state */ } [data-part="label"][data-focus] { /* styles for label focus state */ }

Disabled State

When the color picker is disabled, the data-disabled attribute is added to the label, control, trigger and option parts.

[data-part="label"][data-disabled] { /* styles for label disabled state */ } [data-part="control"][data-disabled] { /* styles for control disabled state */ } [data-part="trigger"][data-disabled] { /* styles for trigger disabled state */ } [data-part="swatch-trigger"][data-disabled] { /* styles for item disabled state */ }

Swatch State

When a swatch's color value matches the color picker's value, the data-state=checked attribute is added to the swatch part.

[data-part="swatch-trigger"][data-state="checked|unchecked"] { /* styles for swatch's checked state */ }

Methods and Properties

Machine Context

The color picker machine exposes the following context properties:

  • idsPartial<{ root: string; control: string; trigger: string; label: string; input: string; hiddenInput: string; content: string; area: string; areaGradient: string; positioner: string; formatSelect: string; areaThumb: string; channelInput(id: string): string; channelSliderTrack(id: ColorChannel): string; }>The ids of the elements in the color picker. Useful for composition.
  • valueColorThe current color value
  • disabledbooleanWhether the color picker is disabled
  • readOnlybooleanWhether the color picker is read-only
  • requiredbooleanWhether the color picker is required
  • invalidbooleanWhether the color picker is invalid
  • onValueChange(details: ValueChangeDetails) => voidHandler that is called when the value changes, as the user drags.
  • onValueChangeEnd(details: ValueChangeDetails) => voidHandler that is called when the user stops dragging.
  • onOpenChange(details: OpenChangeDetails) => voidHandler that is called when the user opens or closes the color picker.
  • namestringThe name for the form input
  • positioningPositioningOptionsThe positioning options for the color picker
  • initialFocusEl() => HTMLElementThe initial focus element when the color picker is opened.
  • openbooleanWhether the color picker is open
  • open.controlledbooleanWhether the color picker open state is controlled by the user
  • formatColorFormatThe color format to use
  • onFormatChange(details: FormatChangeDetails) => voidFunction called when the color format changes
  • closeOnSelectbooleanWhether to close the color picker when a swatch is selected
  • openAutoFocusbooleanWhether to auto focus the color picker when it is opened
  • idstringThe unique identifier of the machine.
  • getRootNode() => Node | ShadowRoot | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
  • dir"ltr" | "rtl"The document's text/writing direction.
  • onPointerDownOutside(event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component
  • onFocusOutside(event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component
  • onInteractOutside(event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component

Machine API

The color picker api exposes the following methods:

  • draggingbooleanWhether the color picker is being dragged
  • openbooleanWhether the color picker is open
  • valueColorThe current color value (as a string)
  • valueAsStringstringThe current color value (as a Color object)
  • setValue(value: string | Color) => voidFunction to set the color value
  • getChannelValue(channel: ColorChannel) => stringFunction to set the color value
  • getChannelValueText(channel: ColorChannel, locale: string) => stringFunction to get the formatted and localized value of a specific channel
  • setChannelValue(channel: ColorChannel, value: number) => voidFunction to set the color value of a specific channel
  • formatColorFormatThe current color format
  • setFormat(format: ColorFormat) => voidFunction to set the color format
  • alphanumberThe alpha value of the color
  • setAlpha(value: number) => voidFunction to set the color alpha
  • setOpen(open: boolean) => voidFunction to open or close the color picker

Data Attributes

Root
data-scope
color-picker
data-part
root
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid
Label
data-scope
color-picker
data-part
label
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid
data-focus
Present when focused
Control
data-scope
color-picker
data-part
control
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid
data-state
"open" | "closed"
data-focus
Present when focused
Trigger
data-scope
color-picker
data-part
trigger
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid
data-placement
The placement of the trigger
data-state
"open" | "closed"
data-focus
Present when focused
Content
data-scope
color-picker
data-part
content
data-placement
The placement of the content
data-state
"open" | "closed"
ValueText
data-scope
color-picker
data-part
value-text
data-disabled
Present when disabled
data-focus
Present when focused
Area
data-scope
color-picker
data-part
area
data-invalid
Present when invalid
data-disabled
Present when disabled
data-readonly
Present when read-only
AreaBackground
data-scope
color-picker
data-part
area-background
data-invalid
Present when invalid
data-disabled
Present when disabled
data-readonly
Present when read-only
AreaThumb
data-scope
color-picker
data-part
area-thumb
data-disabled
Present when disabled
data-invalid
Present when invalid
data-readonly
Present when read-only
ChannelSlider
data-scope
color-picker
data-part
channel-slider
data-channel
The color channel of the channelslider
data-orientation
The orientation of the channelslider
ChannelSliderTrack
data-scope
color-picker
data-part
channel-slider-track
data-channel
The color channel of the channelslidertrack
data-orientation
The orientation of the channelslidertrack
ChannelSliderLabel
data-scope
color-picker
data-part
channel-slider-label
data-channel
The color channel of the channelsliderlabel
ChannelSliderValueText
data-scope
color-picker
data-part
channel-slider-value-text
data-channel
The color channel of the channelslidervaluetext
ChannelSliderThumb
data-scope
color-picker
data-part
channel-slider-thumb
data-channel
The color channel of the channelsliderthumb
data-disabled
Present when disabled
data-orientation
The orientation of the channelsliderthumb
ChannelInput
data-scope
color-picker
data-part
channel-input
data-channel
The color channel of the channelinput
data-disabled
Present when disabled
data-invalid
Present when invalid
data-readonly
Present when read-only
EyeDropperTrigger
data-scope
color-picker
data-part
eye-dropper-trigger
data-disabled
Present when disabled
data-invalid
Present when invalid
data-readonly
Present when read-only
SwatchTrigger
data-scope
color-picker
data-part
swatch-trigger
data-state
"checked" | "unchecked"
data-value
The value of the item
data-disabled
Present when disabled
Swatch
data-scope
color-picker
data-part
swatch
data-state
"checked" | "unchecked"
data-value
The value of the item

Accessibility

Keyboard Interactions

  • Enter
    When focus is on the trigger, opens the color picker
    When focus is on a trigger of a swatch, selects the color (and closes the color picker)
    When focus is on the input or channel inputs, selects the color
  • ArrowLeft
    When focus is on the color area, decreases the hue value of the color
    When focus is on the channel sliders, decreases the value of the channel
  • ArrowRight
    When focus is on the color area, increases the hue value of the color
    When focus is on the channel sliders, increases the value of the channel
  • ArrowUp
    When focus is on the color area, increases the saturation value of the color
    When focus is on the channel sliders, increases the value of the channel
  • ArrowDown
    When focus is on the color area, decreases the saturation value of the color
    When focus is on the channel sliders, decreases the value of the channel
  • Esc
    Closes the color picker and moves focus to the trigger

Edit this page on GitHub

Proudly made in🇳🇬by Segun Adebayo

Copyright © 2024
On this page