ColorPicker
A composable color picker that synchronizes color value between multiple color components
Usage
import { ColorPicker, ColorArea, ColorSlider, ColorSwatch, ColorField, ColorSwatchPicker,} from '@lenso/ui';"use client";
import { ColorArea, ColorPicker, ColorSlider, ColorSwatch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ area: { maxWidth: "100%" }, slider: { gap: 4, paddingInline: 4 }, output: { color: "var(--muted)" },});export function Basic() { return ( <ColorPicker defaultValue="#0485F7"> <ColorPicker.Trigger> <ColorSwatch size="lg" /> <span>Pick a color</span> </ColorPicker.Trigger> <ColorPicker.Popover> <ColorArea aria-label="Color area" xstyle={styles.area} colorSpace="hsb" xChannel="saturation" yChannel="brightness" > <ColorArea.Thumb /> </ColorArea> <ColorSlider aria-label="Hue" channel="hue" xstyle={styles.slider} colorSpace="hsb"> <span>Hue</span> <ColorSlider.Output xstyle={styles.output} /> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> </ColorPicker.Popover> </ColorPicker> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Anatomy
The ColorPicker is a composable component that combines multiple color components:
import { ColorPicker, ColorArea, ColorSlider, ColorSwatch, Label } from '@lenso/ui';
export default () => ( <ColorPicker defaultValue="#0485F7"> <ColorPicker.Trigger> <ColorSwatch /> <Label>Pick a color</Label> </ColorPicker.Trigger> <ColorPicker.Popover> <ColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness"> <ColorArea.Thumb /> </ColorArea> <ColorSlider channel="hue" colorSpace="hsb"> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> </ColorPicker.Popover> </ColorPicker>);Examples
Controlled
"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. Gravity Shuffle replaces the source Iconify reference to the same icon. */import { Button, ColorArea, ColorField, ColorPicker, ColorSlider, ColorSwatch, ColorSwatchPicker, parseColor,} from "@lenso/ui";import { Shuffle } from "@gravity-ui/icons";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { styles } from "./source.stylex";const presets = [ "#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899", "#f43f5e",];export function Controlled() { const [color, setColor] = useState(parseColor("#325578")); function shuffleColor() { const hue = Math.floor(Math.random() * 360); const saturation = 50 + Math.floor(Math.random() * 50); const lightness = 40 + Math.floor(Math.random() * 30); setColor(parseColor(`hsl(${hue}, ${saturation}%, ${lightness}%)`)); } return ( <div {...stylex.props(styles.column)}> <ColorPicker value={color} onChange={setColor}> <ColorPicker.Trigger> <ColorSwatch size="lg" /> <span>Pick a color</span> </ColorPicker.Trigger> <ColorPicker.Popover xstyle={styles.popover}> <ColorSwatchPicker aria-label="Color presets" xstyle={styles.presets} size="xs"> {presets.map((preset) => ( <ColorSwatchPicker.Item key={preset} color={preset}> <ColorSwatchPicker.Swatch /> </ColorSwatchPicker.Item> ))} </ColorSwatchPicker> <ColorArea aria-label="Color area" xstyle={styles.area} colorSpace="hsb" xChannel="saturation" yChannel="brightness" > <ColorArea.Thumb /> </ColorArea> <div {...stylex.props(styles.hueRow)}> <ColorSlider aria-label="Hue slider" channel="hue" xstyle={styles.flex1} colorSpace="hsb" > <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> <Button isIconOnly aria-label="Shuffle color" size="sm" variant="tertiary" onClick={shuffleColor} > <Shuffle {...stylex.props(styles.icon)} /> </Button> </div> <ColorField aria-label="Color field"> <ColorField.Group variant="secondary"> <ColorField.Prefix> <ColorSwatch size="xs" /> </ColorField.Prefix> <ColorField.Input /> </ColorField.Group> </ColorField> </ColorPicker.Popover> </ColorPicker> <p {...stylex.props(styles.selected)}> Selected: <span {...stylex.props(styles.medium)}>{color.toString("hex")}</span> </p> </div> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
With Swatches
"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. */import { ColorArea, ColorPicker, ColorSlider, ColorSwatch, ColorSwatchPicker } from "@lenso/ui";import { styles } from "./source.stylex";const presets = [ "#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899", "#f43f5e",];export function WithSwatches() { return ( <ColorPicker defaultValue="#F43F5E"> <ColorPicker.Trigger> <ColorSwatch size="lg" /> <span>Brand Color</span> </ColorPicker.Trigger> <ColorPicker.Popover> <ColorArea aria-label="Color area" xstyle={styles.area} colorSpace="hsb" xChannel="saturation" yChannel="brightness" > <ColorArea.Thumb /> </ColorArea> <ColorSlider aria-label="Hue slider" channel="hue" xstyle={styles.slider} colorSpace="hsb"> <ColorSlider.Label>Hue</ColorSlider.Label> <ColorSlider.Output xstyle={styles.output} /> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> <ColorSwatchPicker aria-label="Color presets" xstyle={styles.swatches} size="xs"> {presets.map((preset) => ( <ColorSwatchPicker.Item key={preset} color={preset}> <ColorSwatchPicker.Swatch /> </ColorSwatchPicker.Item> ))} </ColorSwatchPicker> </ColorPicker.Popover> </ColorPicker> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
With Fields
Use ColorField to allow users to edit individual color channel values with a Select to switch between color spaces.
"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. */import { ColorArea, ColorField, ColorPicker, ColorSlider, ColorSwatch } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { styles } from "./source.stylex";import { ColorDemoSelect } from "./source";type Space = "hsb" | "hsl" | "rgb";const channels = { hsb: ["hue", "saturation", "brightness"], hsl: ["hue", "saturation", "lightness"], rgb: ["red", "green", "blue"],} as const;export function WithFields() { const [colorSpace, setColorSpace] = useState<Space>("hsl"); return ( <ColorPicker defaultValue="hsla(220, 90%, 50%, 0.8)"> <ColorPicker.Trigger> <ColorSwatch size="lg" /> <span>Pick a color</span> </ColorPicker.Trigger> <ColorPicker.Popover xstyle={styles.narrowPopover}> <ColorArea aria-label="Color area" xstyle={styles.area} colorSpace="hsb" xChannel="saturation" yChannel="brightness" > <ColorArea.Thumb /> </ColorArea> <ColorSlider channel="hue" xstyle={styles.slider} colorSpace="hsb"> <ColorSlider.Label>Hue</ColorSlider.Label> <ColorSlider.Output xstyle={styles.output} /> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> <ColorDemoSelect label="Color space" value={colorSpace} options={["hsb", "hsl", "rgb"]} onChange={setColorSpace} uppercase /> <div {...stylex.props(styles.channelGrid)}> {channels[colorSpace].map((channel) => ( <ColorField key={channel} aria-label={channel} channel={channel} colorSpace={colorSpace} > <ColorField.Group variant="secondary"> <ColorField.Input /> </ColorField.Group> </ColorField> ))} </div> </ColorPicker.Popover> </ColorPicker> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
With Sliders
Use multiple ColorSlider components to adjust each channel of a color value.
"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. */import { ColorPicker, ColorSlider, ColorSwatch } from "@lenso/ui";import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { styles } from "./source.stylex";import { ColorDemoSelect } from "./source";type Space = "hsb" | "hsl" | "rgb";const channelProps = { hsb: [ { colorSpace: "hsb", channel: "hue" }, { colorSpace: "hsb", channel: "saturation" }, { colorSpace: "hsb", channel: "brightness" }, { colorSpace: "hsb", channel: "alpha" }, ], hsl: [ { colorSpace: "hsl", channel: "hue" }, { colorSpace: "hsl", channel: "saturation" }, { colorSpace: "hsl", channel: "lightness" }, { colorSpace: "hsl", channel: "alpha" }, ], rgb: [ { colorSpace: "rgb", channel: "red" }, { colorSpace: "rgb", channel: "green" }, { colorSpace: "rgb", channel: "blue" }, { colorSpace: "rgb", channel: "alpha" }, ],} as const;export function WithSliders() { const [colorSpace, setColorSpace] = useState<Space>("hsl"); return ( <ColorPicker defaultValue="hsl(219, 58%, 93%)"> <ColorPicker.Trigger> <ColorSwatch size="lg" /> <span>Pick a color</span> </ColorPicker.Trigger> <ColorPicker.Popover xstyle={styles.slidersPopover}> <ColorDemoSelect label="Color space" value={colorSpace} options={["hsb", "hsl", "rgb"]} onChange={setColorSpace} uppercase /> <div {...stylex.props(styles.column2)}> {channelProps[colorSpace].map((props) => ( <ColorSlider key={props.channel} {...props} aria-label={props.channel} xstyle={styles.slider} > <ColorSlider.Label xstyle={styles.capitalize}>{props.channel}</ColorSlider.Label> <ColorSlider.Output xstyle={styles.output} /> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> ))} </div> </ColorPicker.Popover> </ColorPicker> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Customization
Tailwind CSS
"use client";/** Adapted from HeroUI v3.2.6. Apache-2.0. */import { ColorArea, ColorPicker, ColorSlider, ColorSwatch } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { styles } from "./source.stylex";export function CustomStyles() { return ( <ColorPicker defaultValue="#0485F7"> <ColorPicker.Trigger xstyle={styles.trigger}> <ColorSwatch size="lg" /> <span {...stylex.props(styles.label)}>Theme color</span> </ColorPicker.Trigger> <ColorPicker.Popover xstyle={styles.surface}> <ColorArea aria-label="Color area" xstyle={styles.area} colorSpace="hsb" xChannel="saturation" yChannel="brightness" > <ColorArea.Thumb /> </ColorArea> <ColorSlider channel="hue" xstyle={styles.slider} colorSpace="hsb"> <ColorSlider.Label>Hue</ColorSlider.Label> <ColorSlider.Output xstyle={styles.output} /> <ColorSlider.Track> <ColorSlider.Thumb /> </ColorSlider.Track> </ColorSlider> </ColorPicker.Popover> </ColorPicker> );}Local adaptation source above. Derived from HeroUI v3.2.6 source.
Global CSS
To customize the ColorPicker component classes, you can use the @layer components directive.
Learn more.
@layer components { .color-picker { @apply inline-flex; }
.color-picker__trigger { @apply inline-flex items-center gap-4 rounded-lg; }
.color-picker__popover { @apply p-4 rounded-xl; }}Styling Reference
HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The ColorPicker component uses these CSS classes (View source styles):
Base Classes [!toc]
.color-picker- Base container.color-picker__trigger- Trigger button.color-picker__popover- Popover container
Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- Focus:
:focus-visibleor[data-focus-visible="true"] - Disabled:
:disabledor[data-disabled="true"]
API Reference
ColorPicker
Inherits from React Aria ColorPicker.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Color | - | The current color value (controlled) |
defaultValue | string | Color | - | The default color value (uncontrolled) |
onChange | (color: Color) => void | - | Handler called when the color changes |
children | React.ReactNode | - | Content of the color picker (Trigger, Popover, etc.) |
className | string | - | Additional CSS classes |
ColorPicker.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | ((renderProps) => React.ReactNode) | - | Trigger content or render prop |
className | string | - | Additional CSS classes |
ColorPicker.Popover
| Prop | Type | Default | Description |
|---|---|---|---|
placement | Placement | "bottom left" | Placement of the popover |
children | React.ReactNode | - | Popover content |
className | string | - | Additional CSS classes |
Related Types
Color
Represents a color value. See React Aria Color for full API.
| Method | Description |
|---|---|
toString(format) | Converts the color to a string in the given format (hex, rgb, hsl, hsb, css) |
toFormat(format) | Converts the color to the given format and returns a new Color object |
getChannelValue(channel) | Returns the numeric value for a given channel |
withChannelValue(channel, value) | Sets the numeric value for a channel and returns a new Color |
parseColor
import { parseColor } from 'react-aria-components';
// Parse from stringconst color = parseColor('#ff0000');const hslColor = parseColor('hsl(0, 100%, 50%)');