Skip to content
Destyler UI Destyler UI Destyler UI

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.

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/{color-picker,vue}
Terminal window
      
        
npm install @destyler/{color-picker,react}
Terminal window
      
        
npm install @destyler/{color-picker,svelte}
Terminal window
      
        
npm install @destyler/{color-picker,solid}

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as colorPicker from '@destyler/color-picker'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(colorPicker.machine({
id: useId(),
value: colorPicker.parse('hsl(240,5.9%,10%)'),
}))
const api = computed(() => colorPicker.connect(state.value, send, normalizeProps))
</script>
<template>
<div v-bind="api.getRootProps()">
<input v-bind="api.getHiddenInputProps()">
<div v-bind="api.getControlProps()">
<button v-bind="api.getTriggerProps()" >
<div v-bind="api.getTransparencyGridProps({ size: '8px' })" />
<div v-bind="api.getSwatchProps({ value: api.value })" />
</button>
<input v-bind="api.getChannelInputProps({ channel: 'hex' })">
<input v-bind="api.getChannelInputProps({ channel: 'alpha' })">
</div>
<Teleport v-if="api.open" to="body">
<div v-bind="api.getPositionerProps()" >
<div v-bind="api.getContentProps()" >
<div v-bind="api.getAreaProps()" >
<div v-bind="api.getAreaBackgroundProps()" />
<div v-bind="api.getAreaThumbProps()" />
</div>
<div v-bind="api.getChannelSliderProps({ channel: 'hue' })" >
<div v-bind="api.getChannelSliderTrackProps({ channel: 'hue' })" />
<div v-bind="api.getChannelSliderThumbProps({ channel: 'hue' })" />
</div>
<div v-bind="api.getChannelSliderProps({ channel: 'alpha' })" >
<div v-bind="api.getTransparencyGridProps({ size: '8px' })" />
<div v-bind="api.getChannelSliderTrackProps({ channel: 'alpha' })" />
<div v-bind="api.getChannelSliderThumbProps({ channel: 'alpha' })" />
</div>
</div>
</div>
</Teleport>
</div>
</template>
import * as colorPicker from '@destyler/color-picker'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId } from 'react'
import { createPortal } from 'react-dom'
export default function ColorPicker() {
const id = useId()
const [state, send] = useMachine(colorPicker.machine({
id,
value: colorPicker.parse('hsl(240,5.9%,10%)'),
}))
const api = colorPicker.connect(state, send, normalizeProps)
return (
<div {...api.getRootProps()} >
<input {...api.getHiddenInputProps()} />
<div {...api.getControlProps()} >
<button {...api.getTriggerProps()}>
<div {...api.getTransparencyGridProps({ size: '8px' })} />
<div {...api.getSwatchProps({ value: api.value })} />
</button>
<input {...api.getChannelInputProps({ channel: 'hex' })}/>
<input {...api.getChannelInputProps({ channel: 'alpha' })}/>
</div>
{api.open && createPortal(
<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: '8px' })} />
<div {...api.getChannelSliderTrackProps({ channel: 'alpha' })} />
<div {...api.getChannelSliderThumbProps({ channel: 'alpha' })} />
</div>
</div>
</div>,
document.body
)}
</div>
)
}
<script lang="ts">
import * as colorPicker from '@destyler/color-picker'
import { normalizeProps, useMachine, portal } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(colorPicker.machine({
id,
value: colorPicker.parse('hsl(240,5.9%,10%)'),
}))
const api = $derived(colorPicker.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<input {...api.getHiddenInputProps()}>
<div {...api.getControlProps()}>
<button {...api.getTriggerProps()}>
<div {...api.getTransparencyGridProps({ size: '8px' })} ></div>
<div {...api.getSwatchProps({ value: api.value })} ></div>
</button>
<input {...api.getChannelInputProps({ channel: 'hex' })}>
<input {...api.getChannelInputProps({ channel: 'alpha' })}>
</div>
{#if api.open}
<div use:portal>
<div {...api.getPositionerProps()}>
<div {...api.getContentProps()} >
<div {...api.getAreaProps()} >
<div {...api.getAreaBackgroundProps()} ></div>
<div {...api.getAreaThumbProps()} ></div>
</div>
<div {...api.getChannelSliderProps({ channel: 'hue' })} >
<div {...api.getChannelSliderTrackProps({ channel: 'hue' })} ></div>
<div {...api.getChannelSliderThumbProps({ channel: 'hue' })} ></div>
</div>
<div {...api.getChannelSliderProps({ channel: 'alpha' })} >
<div {...api.getTransparencyGridProps({ size: '8px' })} ></div>
<div {...api.getChannelSliderTrackProps({ channel: 'alpha' })} ></div>
<div {...api.getChannelSliderThumbProps({ channel: 'alpha' })} ></div>
</div>
</div>
</div>
</div>
{/if}
</div>
import * as colorPicker from '@destyler/color-picker'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
import { Portal } from 'solid-js/web'
export default function ColorPicker() {
const [state, send] = useMachine(colorPicker.machine({
id: createUniqueId(),
value: colorPicker.parse('hsl(240,5.9%,10%)'),
}))
const api = createMemo(() => colorPicker.connect(state, send, normalizeProps))
return (
<div {...api().getRootProps()}>
<input {...api().getHiddenInputProps()} />
<div {...api().getControlProps()} >
<button {...api().getTriggerProps()}>
<div {...api().getTransparencyGridProps({ size: '8px' })} />
<div {...api().getSwatchProps({ value: api().value })} />
</button>
<input {...api().getChannelInputProps({ channel: 'hex' })}/>
<input {...api().getChannelInputProps({ channel: 'alpha' })}/>
</div>
{api().open && (
<Portal mount={document.body}>
<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: '8px' })} />
<div {...api().getChannelSliderTrackProps({ channel: 'alpha' })} />
<div {...api().getChannelSliderThumbProps({ channel: 'alpha' })} />
</div>
</div>
</div>
</Portal>
)}
</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 }
},
}),
)

Using a custom color format

By default, the color picker's output format is rgba. You can change this format to eitherhslaorhsbaby using theformat` 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 }
},
}),
)

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.

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

Earlier, we mentioned that each collapse part has a data-part attribute added to them to select and style them in the DOM.

Open and closed state

When a color picker is expanded or collapsed, a data-state attribute is set on the root, trigger and content elements. This attribute is removed when it is closed.

[data-part="control"][data-state="open"] {
/* styles for control open or state */
}
[data-part="trigger"][data-state="open"] {
/* styles for control open or state */
}
[data-part="content"][data-state="open"] {
/* 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:

ids
Partial<{ 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.
value
Color
The current color value
disabled
boolean
Whether the color picker is disabled
readOnly
boolean
Whether the color picker is read-only
required
boolean
Whether the color picker is required
invalid
boolean
Whether the color picker is invalid
onValueChange
(details: ValueChangeDetails) => void
Handler that is called when the value changes, as the user drags.
onValueChangeEnd
(details: ValueChangeDetails) => void
Handler that is called when the user stops dragging.
onOpenChange
(details: OpenChangeDetails) => void
Handler that is called when the user opens or closes the color picker.
name
string
The name for the form input
positioning
PositioningOptions
The positioning options for the color picker
initialFocusEl
() => HTMLElement
The initial focus element when the color picker is opened.
open
boolean
Whether the color picker is open
open.controlled
boolean
Whether the color picker open state is controlled by the user
format
ColorFormat
The color format to use
onFormatChange
(details: FormatChangeDetails) => void
Function called when the color format changes
closeOnSelect
boolean
Whether to close the color picker when a swatch is selected
openAutoFocus
boolean
Whether to auto focus the color picker when it is opened
id
string
The unique identifier of the machine.
getRootNode
() => Node | ShadowRoot | Document
A 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) => void
Function called when the pointer is pressed down outside the component
onFocusOutside
(event: FocusOutsideEvent) => void
Function called when the focus is moved outside the component
onInteractOutside
(event: InteractOutsideEvent) => void
Function called when an interaction happens outside the component

Machine API

The color picker api exposes the following methods:

dragging
boolean
Whether the color picker is being dragged
open
boolean
Whether the color picker is open
value
Color
The current color value (as a Color object)
valueAsString
string
The current color value (as a string)
setValue
(value: string | Color) => void
Function to set the color value
getChannelValue
(channel: ColorChannel) => string
Function to get the value of a specific channel
getChannelValueText
(channel: ColorChannel, locale: string) => string
Function to get the formatted and localized value of a specific channel
setChannelValue
(channel: ColorChannel, value: number) => void
Function to set the color value of a specific channel
format
ColorFormat
The current color format
setFormat
(format: ColorFormat) => void
Function to set the color format
alpha
number
The alpha value of the color
setAlpha
(value: number) => void
Function to set the color alpha
setOpen
(open: boolean) => void
Function to open or close the color picker

Data Attributes

Root

name
desc
data-scope
color-picker
data-part
root
data-disabled
Present when disabled
data-readonly
Present when read-only
data-invalid
Present when invalid

Label

name
desc
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

name
desc
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

name
desc
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

name
desc
data-scope
color-picker
data-part
content
data-placement
The placement of the content
data-state
"open" | "closed"

ValueText

name
desc
data-scope
color-picker
data-part
value-text
data-disabled
Present when disabled
data-focus
Present when focused

Area

name
desc
data-scope
color-picker
data-part
area
data-invalid
Present when invalid
data-disabled
Present when disabled
data-readonly
Present when read-only

AreaBackground

name
desc
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

name
desc
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

name
desc
data-scope
color-picker
data-part
channel-slider
data-channel
The color channel of the channelslider
data-orientation
The orientation of the channelslider

ChannelSliderTrack

name
desc
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

name
desc
data-scope
color-picker
data-part
channel-slider-label
data-channel
The color channel of the channelsliderlabel

ChannelSliderValueText

name
desc
data-scope
color-picker
data-part
channel-slider-value-text
data-channel
The color channel of the channelslidervaluetext

ChannelSliderThumb

name
desc
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

name
desc
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

name
desc
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

name
desc
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

name
desc
data-scope
color-picker
data-part
swatch
data-state
"checked" | "unchecked"
data-value
The value of the item

Accessibility

Keyboard Interaction

name
desc
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