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.
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 } }, }),)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 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-partattribute 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:
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; channelSliderThumb: (id: ColorChannel) => string; }>Colorbooleanbooleanbooleanboolean(details: ValueChangeDetails) => void(details: ValueChangeDetails) => void(details: OpenChangeDetails) => voidstringPositioningOptions() => HTMLElementbooleanbooleanColorFormat(details: FormatChangeDetails) => voidbooleanbooleanstring() => Node | ShadowRoot | Document"ltr" | "rtl"(event: PointerDownOutsideEvent) => void(event: FocusOutsideEvent) => void(event: InteractOutsideEvent) => voidMachine API
The color picker api exposes the following methods:
booleanbooleanColorstring(value: string | Color) => void(channel: ColorChannel) => string(channel: ColorChannel, locale: string) => string(channel: ColorChannel, value: number) => voidColorFormat(format: ColorFormat) => voidnumber(value: number) => void(open: boolean) => voidData Attributes
Root
data-scopedata-partdata-disableddata-readonlydata-invalidLabel
data-scopedata-partdata-disableddata-readonlydata-invaliddata-focusControl
data-scopedata-partdata-disableddata-readonlydata-invaliddata-statedata-focusTrigger
data-scopedata-partdata-disableddata-readonlydata-invaliddata-placementdata-statedata-focusContent
data-scopedata-partdata-placementdata-stateValueText
data-scopedata-partdata-disableddata-focusArea
data-scopedata-partdata-invaliddata-disableddata-readonlyAreaBackground
data-scopedata-partdata-invaliddata-disableddata-readonlyAreaThumb
data-scopedata-partdata-disableddata-invaliddata-readonlyChannelSlider
data-scopedata-partdata-channeldata-orientationChannelSliderTrack
data-scopedata-partdata-channeldata-orientationChannelSliderLabel
data-scopedata-partdata-channelChannelSliderValueText
data-scopedata-partdata-channelChannelSliderThumb
data-scopedata-partdata-channeldata-disableddata-orientationChannelInput
data-scopedata-partdata-channeldata-disableddata-invaliddata-readonlyEyeDropperTrigger
data-scopedata-partdata-disableddata-invaliddata-readonlySwatchTrigger
data-scopedata-partdata-statedata-valuedata-disabledSwatch
data-scopedata-partdata-statedata-valueAccessibility
Keyboard Interaction
EnterArrowLeftArrowRightArrowUpArrowDownEsc