UI/UI
Color Picker
A comprehensive color picker component for selecting colors with multiple input methods and presets.
<ColorPickerWithPresets
defaultValue="#3b82f6"
onChange={(color) => console.log("Selected color:", color)}
/>
Installation
Install following dependencies:
npm install react-aria-components class-variance-authority lucide-react
pnpm add react-aria-components class-variance-authority lucide-react
yarn add react-aria-components class-variance-authority lucide-react
bun add react-aria-components class-variance-authority lucide-react
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import {
ColorArea as AriaColorArea,
ColorAreaProps as AriaColorAreaProps,
ColorField as AriaColorField,
ColorFieldProps as AriaColorFieldProps,
ColorPicker as AriaColorPicker,
ColorPickerProps as AriaColorPickerProps,
ColorSlider as AriaColorSlider,
ColorSliderProps as AriaColorSliderProps,
ColorSwatch as AriaColorSwatch,
ColorSwatchPicker as AriaColorSwatchPicker,
ColorSwatchPickerItem as AriaColorSwatchPickerItem,
ColorSwatchPickerItemProps as AriaColorSwatchPickerItemProps,
ColorSwatchPickerProps as AriaColorSwatchPickerProps,
ColorSwatchProps as AriaColorSwatchProps,
ColorThumb as AriaColorThumb,
ColorThumbProps as AriaColorThumbProps,
SliderTrack as AriaSliderTrack,
SliderTrackProps as AriaSliderTrackProps,
ColorPickerStateContext,
composeRenderProps,
parseColor,
FieldError,
Label,
Input,
Group,
} from "react-aria-components";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { Pipette } from "lucide-react";
const colorPickerVariants = cva(
"flex flex-col gap-2 rounded-[var(--radius)] border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-background))] p-4 ",
{
variants: {
size: {
sm: "max-w-xs",
default: "max-w-sm",
lg: "max-w-md",
},
},
defaultVariants: {
size: "default",
},
},
);
export interface ColorPickerProps
extends AriaColorPickerProps,
VariantProps<typeof colorPickerVariants> {
className?: string;
}
function ColorPicker({
className,
size,
children,
...props
}: ColorPickerProps) {
return (
<div className={cn(colorPickerVariants({ size }), className)}>
<AriaColorPicker {...props}>{children}</AriaColorPicker>
</div>
);
}
function ColorField({ className, ...props }: AriaColorFieldProps) {
return (
<AriaColorField
className={composeRenderProps(className, (className) =>
cn("flex flex-col gap-2", className),
)}
{...props}
/>
);
}
function ColorInput({
className,
...props
}: React.ComponentProps<typeof Input>) {
return (
<Input
className={composeRenderProps(className, (className) =>
cn(
"flex h-9 w-full rounded-md border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-background))] px-3 py-1 text-sm text-[hsl(var(--hu-foreground))] transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[hsl(var(--hu-muted-foreground))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--hu-ring))] disabled:cursor-not-allowed disabled:opacity-50",
className,
),
)}
{...props}
/>
);
}
function ColorLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
className={cn(
"text-sm font-medium leading-none text-[hsl(var(--hu-foreground))] peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className,
)}
{...props}
/>
);
}
function ColorArea({ className, ...props }: AriaColorAreaProps) {
return (
<AriaColorArea
className={composeRenderProps(className, (className) =>
cn(
"h-[200px] w-full rounded-[var(--radius)] border border-[hsl(var(--hu-border))] bg-gradient-to-br from-white to-black",
className,
),
)}
{...props}
/>
);
}
function ColorSlider({ className, ...props }: AriaColorSliderProps) {
return (
<AriaColorSlider
className={composeRenderProps(className, (className) =>
cn(
"flex h-8 w-full flex-col gap-2 items-center justify-center",
className,
),
)}
{...props}
/>
);
}
function SliderTrack({ className, style, ...props }: AriaSliderTrackProps) {
return (
<AriaSliderTrack
className={composeRenderProps(className, (className) =>
cn(
"relative h-3 w-full rounded-full border border-[hsl(var(--hu-border))]",
className,
),
)}
style={({ defaultStyle }) => ({
...style,
background: `${defaultStyle.background},
repeating-conic-gradient(
#ccc 0 90deg,
#fff 0 180deg)
0% 0%/8px 8px`,
})}
{...props}
/>
);
}
function ColorThumb({ className, ...props }: AriaColorThumbProps) {
return (
<AriaColorThumb
className={composeRenderProps(className, (className) =>
cn(
"z-10 h-4 w-4 rounded-full border-2 border-white shadow-md ring-1 ring-black/10 focus:outline-none focus:ring-2 focus:ring-[hsl(var(--hu-ring))] focus:ring-offset-2 data-[focus-visible]:ring-2 data-[focus-visible]:ring-[hsl(var(--hu-ring))]",
className,
),
)}
{...props}
/>
);
}
function ColorSwatchPicker({
className,
...props
}: AriaColorSwatchPickerProps) {
return (
<AriaColorSwatchPicker
className={composeRenderProps(className, (className) =>
cn("flex flex-wrap gap-2", className),
)}
{...props}
/>
);
}
function ColorSwatchPickerItem({
className,
...props
}: AriaColorSwatchPickerItemProps) {
return (
<AriaColorSwatchPickerItem
className={composeRenderProps(className, (className) =>
cn(
"group/swatch-item cursor-pointer rounded-[var(--radius)] p-1 focus:outline-none focus:ring-2 focus:ring-[hsl(var(--hu-ring))] focus:ring-offset-2",
className,
),
)}
{...props}
/>
);
}
function ColorSwatch({ className, style, ...props }: AriaColorSwatchProps) {
return (
<AriaColorSwatch
className={composeRenderProps(className, (className) =>
cn(
"h-8 w-8 rounded-md border border-[hsl(var(--hu-border))] group-data-[selected]/swatch-item:ring-2 group-data-[selected]/swatch-item:ring-[hsl(var(--hu-ring))] group-data-[selected]/swatch-item:ring-offset-2",
className,
),
)}
style={({ defaultStyle }) => ({
...style,
background: `${defaultStyle.background},
repeating-conic-gradient(
#ccc 0 90deg,
#fff 0 180deg)
0% 0%/8px 8px`,
})}
{...props}
/>
);
}
const EyeDropperButton = React.forwardRef<
HTMLButtonElement,
React.HTMLAttributes<HTMLButtonElement>
>(({ className, ...props }, ref) => {
const state = React.useContext(ColorPickerStateContext);
if (!state || typeof window === "undefined" || !("EyeDropper" in window)) {
return null;
}
const handleEyeDropper = async () => {
try {
// @ts-ignore - EyeDropper API is not yet in TypeScript DOM types
const eyeDropper = new window.EyeDropper();
const result = await eyeDropper.open();
state.setColor(parseColor(result.sRGBHex));
} catch (error) {
// User cancelled or error occurred
console.warn("EyeDropper operation cancelled or failed:", error);
}
};
return (
<button
ref={ref}
type="button"
className={cn(
"inline-flex h-9 w-9 items-center justify-center rounded-md border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-background))] text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--hu-ring))] disabled:pointer-events-none disabled:opacity-50",
className,
)}
onClick={handleEyeDropper}
aria-label="Pick color from screen"
{...props}
>
<Pipette className="h-4 w-4" />
</button>
);
});
EyeDropperButton.displayName = "EyeDropperButton";
function ColorError({
className,
...props
}: React.ComponentProps<typeof FieldError>) {
return (
<FieldError
className={composeRenderProps(className, (className) =>
cn("text-sm font-medium text-[hsl(var(--hu-destructive))]", className),
)}
{...props}
/>
);
}
export {
ColorPicker,
ColorField,
ColorInput,
ColorLabel,
ColorArea,
ColorSlider,
SliderTrack,
ColorThumb,
ColorSwatchPicker,
ColorSwatchPickerItem,
ColorSwatch,
EyeDropperButton,
ColorError,
Group as ColorGroup,
};
"use client";
import * as React from "react";
import {
ColorPicker,
ColorField,
ColorInput,
ColorLabel,
ColorArea,
ColorSlider,
SliderTrack,
ColorThumb,
ColorSwatchPicker,
ColorSwatchPickerItem,
ColorSwatch,
EyeDropperButton,
ColorError,
ColorGroup,
} from "./color-picker";
import { parseColor, Color } from "react-aria-components";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../Select/select";
import {
type ColorFormat,
formatLabels,
formatColorValue,
parseColorFromFormat,
getFormatPlaceholder,
} from "./color-utils";
interface ColorPickerWithFormatsProps {
value?: string;
defaultValue?: string;
onChange?: (color: string) => void;
presets?: string[];
showEyeDropper?: boolean;
showFormatSelector?: boolean;
defaultFormat?: ColorFormat;
className?: string;
size?: "sm" | "default" | "lg";
}
const defaultPresets = [
"#000000",
"#ffffff",
"#ff0000",
"#00ff00",
"#0000ff",
"#ffff00",
"#ff00ff",
"#00ffff",
];
function ColorPickerWithFormats({
value,
defaultValue = "#000000",
onChange,
presets = defaultPresets,
showEyeDropper = true,
showFormatSelector = true,
defaultFormat = "hex",
className,
size = "default",
}: ColorPickerWithFormatsProps) {
const [colorValue, setColorValue] = React.useState(
value ? parseColor(value) : parseColor(defaultValue),
);
const [currentFormat, setCurrentFormat] =
React.useState<ColorFormat>(defaultFormat);
const [inputValue, setInputValue] = React.useState("");
React.useEffect(() => {
if (value) {
const parsed = parseColor(value);
setColorValue(parsed);
setInputValue(formatColorValue(parsed, currentFormat));
}
}, [value, currentFormat]);
React.useEffect(() => {
setInputValue(formatColorValue(colorValue, currentFormat));
}, [colorValue, currentFormat]);
const handleColorChange = React.useCallback(
(color: Color) => {
setColorValue(color);
const formattedValue = formatColorValue(color, currentFormat);
setInputValue(formattedValue);
onChange?.(color.toString("hex"));
},
[onChange, currentFormat],
);
const handleInputChange = React.useCallback(
(value: string) => {
setInputValue(value);
const parsed = parseColorFromFormat(value, currentFormat);
if (parsed) {
setColorValue(parsed);
onChange?.(parsed.toString("hex"));
}
},
[currentFormat, onChange],
);
const handleFormatChange = React.useCallback(
(format: ColorFormat) => {
setCurrentFormat(format);
setInputValue(formatColorValue(colorValue, format));
},
[colorValue],
);
return (
<ColorPicker
value={colorValue}
onChange={handleColorChange}
size={size}
className={className}
>
<div className="space-y-4">
<ColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness">
<ColorThumb />
</ColorArea>
<ColorSlider colorSpace="hsb" channel="hue">
<SliderTrack>
<ColorThumb />
</SliderTrack>
</ColorSlider>
<ColorSlider colorSpace="hsb" channel="alpha">
<SliderTrack>
<ColorThumb />
</SliderTrack>
</ColorSlider>
<div className="space-y-2">
{showFormatSelector && (
<div className="flex items-center gap-2">
<ColorLabel className="min-w-0 shrink-0">Format</ColorLabel>
<Select
value={currentFormat}
onValueChange={(value) =>
handleFormatChange(value as ColorFormat)
}
>
<SelectTrigger className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(formatLabels).map(([format, label]) => (
<SelectItem key={format} value={format}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}{" "}
<ColorField>
<ColorLabel>{formatLabels[currentFormat]} Value</ColorLabel>
<ColorGroup className="flex gap-2">
<ColorInput
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
placeholder={getFormatPlaceholder(currentFormat)}
/>
{showEyeDropper && <EyeDropperButton />}
</ColorGroup>
<ColorError />
</ColorField>
</div>
{presets.length > 0 && (
<div className="space-y-2">
<ColorLabel>Presets</ColorLabel>
<ColorSwatchPicker>
{presets.map((preset, index) => (
<ColorSwatchPickerItem key={index} color={parseColor(preset)}>
<ColorSwatch />
</ColorSwatchPickerItem>
))}
</ColorSwatchPicker>
</div>
)}
</div>
</ColorPicker>
);
}
export {
ColorPickerWithFormats,
type ColorPickerWithFormatsProps,
type ColorFormat,
};
"use client";
import * as React from "react";
import {
ColorPicker,
ColorField,
ColorInput,
ColorLabel,
ColorArea,
ColorSlider,
SliderTrack,
ColorThumb,
ColorSwatchPicker,
ColorSwatchPickerItem,
ColorSwatch,
EyeDropperButton,
ColorError,
ColorGroup,
} from "./color-picker";
import { parseColor } from "react-aria-components";
interface ColorPickerWithPresetsProps {
value?: string;
defaultValue?: string;
onChange?: (color: string) => void;
presets?: string[];
showEyeDropper?: boolean;
showInput?: boolean;
className?: string;
size?: "sm" | "default" | "lg";
}
const defaultPresets = [
"#000000",
"#ffffff",
"#ff0000",
"#00ff00",
"#0000ff",
"#ffff00",
"#ff00ff",
];
function ColorPickerWithPresets({
value,
defaultValue = "#000000",
onChange,
presets = defaultPresets,
showEyeDropper = true,
showInput = true,
className,
size = "default",
}: ColorPickerWithPresetsProps) {
const [colorValue, setColorValue] = React.useState(
value ? parseColor(value) : parseColor(defaultValue),
);
React.useEffect(() => {
if (value) {
setColorValue(parseColor(value));
}
}, [value]);
const handleColorChange = React.useCallback(
(color: any) => {
setColorValue(color);
onChange?.(color.toString("hex"));
},
[onChange],
);
return (
<ColorPicker
value={colorValue}
onChange={handleColorChange}
size={size}
className={className}
>
<div className="space-y-4">
<ColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness">
<ColorThumb />
</ColorArea>
<ColorSlider colorSpace="hsb" channel="hue">
<SliderTrack>
<ColorThumb />
</SliderTrack>
</ColorSlider>
<ColorSlider colorSpace="hsb" channel="alpha">
<SliderTrack>
<ColorThumb />
</SliderTrack>
</ColorSlider>
{showInput && (
<ColorField>
<ColorLabel>Hex Color</ColorLabel>
<ColorGroup className="flex gap-2">
<ColorInput />
{showEyeDropper && <EyeDropperButton />}
</ColorGroup>
<ColorError />
</ColorField>
)}
{presets.length > 0 && (
<div className="space-y-2">
<ColorLabel>Presets</ColorLabel>
<ColorSwatchPicker>
{presets.map((preset) => (
<ColorSwatchPickerItem key={preset} color={parseColor(preset)}>
<ColorSwatch />
</ColorSwatchPickerItem>
))}
</ColorSwatchPicker>
</div>
)}
</div>
</ColorPicker>
);
}
export { ColorPickerWithPresets };
Now create new file components/ui/ColorPicker/color-utils.ts
and add the following code:
/**
* Enhanced color format utilities for the ColorPicker component
* Provides conversion between different color formats including OKLCH and LAB
*/
import { Color, parseColor } from "react-aria-components";
export type ColorFormat = "hex" | "rgb" | "hsl" | "hsv" | "oklch" | "lab";
export const formatLabels: Record<ColorFormat, string> = {
hex: "HEX",
rgb: "RGB",
hsl: "HSL",
hsv: "HSV",
oklch: "OKLCH",
lab: "LAB",
};
/**
* Converts RGB values (0-1) to XYZ color space
*/
function rgbToXyz(r: number, g: number, b: number): [number, number, number] {
// Convert sRGB to linear RGB
const toLinear = (c: number) => {
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
};
const rLinear = toLinear(r);
const gLinear = toLinear(g);
const bLinear = toLinear(b);
// Convert to XYZ using sRGB matrix
const x = rLinear * 0.4124564 + gLinear * 0.3575761 + bLinear * 0.1804375;
const y = rLinear * 0.2126729 + gLinear * 0.7151522 + bLinear * 0.072175;
const z = rLinear * 0.0193339 + gLinear * 0.119192 + bLinear * 0.9503041;
return [x, y, z];
}
/**
* Converts XYZ to LAB color space
*/
function xyzToLab(x: number, y: number, z: number): [number, number, number] {
// Reference white point D65
const xn = 0.95047;
const yn = 1.0;
const zn = 1.08883;
const fx = x / xn;
const fy = y / yn;
const fz = z / zn;
const transform = (t: number) => {
return t > 0.008856 ? Math.pow(t, 1 / 3) : 7.787 * t + 16 / 116;
};
const fxT = transform(fx);
const fyT = transform(fy);
const fzT = transform(fz);
const L = 116 * fyT - 16;
const a = 500 * (fxT - fyT);
const b = 200 * (fyT - fzT);
return [L, a, b];
}
/**
* Converts XYZ to OKLCH color space (simplified conversion)
*/
function xyzToOklch(x: number, y: number, z: number): [number, number, number] {
// Simplified conversion to OKLCH
// In practice, you'd want to use a proper color library like colorjs.io
// Convert to OKLab first (simplified)
const l = Math.cbrt(0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z);
const m = Math.cbrt(0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z);
const s = Math.cbrt(0.0482003018 * x + 0.2643662691 * y + 0.633851707 * z);
const okL = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
const okA = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
const okB = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
// Convert to LCH
const L_oklch = okL;
const C = Math.sqrt(okA * okA + okB * okB);
const H = (Math.atan2(okB, okA) * 180) / Math.PI;
return [L_oklch, C, H < 0 ? H + 360 : H];
}
/**
* Formats a color value according to the specified format
*/
export function formatColorValue(color: Color, format: ColorFormat): string {
switch (format) {
case "hex":
return color.toString("hex");
case "rgb": {
const rgb = color.toFormat("rgb");
const r = Math.round(rgb.getChannelValue("red"));
const g = Math.round(rgb.getChannelValue("green"));
const b = Math.round(rgb.getChannelValue("blue"));
const alpha = rgb.getChannelValue("alpha");
if (alpha < 1) {
return `rgba(${r}, ${g}, ${b}, ${alpha.toFixed(2)})`;
}
return `rgb(${r}, ${g}, ${b})`;
}
case "hsl": {
const hsl = color.toFormat("hsl");
const h = Math.round(hsl.getChannelValue("hue"));
const s = Math.round(hsl.getChannelValue("saturation"));
const l = Math.round(hsl.getChannelValue("lightness"));
const alpha = hsl.getChannelValue("alpha");
if (alpha < 1) {
return `hsla(${h}, ${s}%, ${l}%, ${alpha.toFixed(2)})`;
}
return `hsl(${h}, ${s}%, ${l}%)`;
}
case "hsv": {
const hsv = color.toFormat("hsb"); // HSB is HSV in react-aria-components
const h = Math.round(hsv.getChannelValue("hue"));
const s = Math.round(hsv.getChannelValue("saturation"));
const v = Math.round(hsv.getChannelValue("brightness"));
const alpha = hsv.getChannelValue("alpha");
if (alpha < 1) {
return `hsva(${h}, ${s}%, ${v}%, ${alpha.toFixed(2)})`;
}
return `hsv(${h}, ${s}%, ${v}%)`;
}
case "oklch": {
const rgb = color.toFormat("rgb");
const r = rgb.getChannelValue("red") / 255;
const g = rgb.getChannelValue("green") / 255;
const b = rgb.getChannelValue("blue") / 255;
const alpha = rgb.getChannelValue("alpha");
const [x, y, z] = rgbToXyz(r, g, b);
const [L, C, H] = xyzToOklch(x, y, z);
if (alpha < 1) {
return `oklch(${(L * 100).toFixed(1)}% ${C.toFixed(3)} ${H.toFixed(
1,
)} / ${alpha.toFixed(2)})`;
}
return `oklch(${(L * 100).toFixed(1)}% ${C.toFixed(3)} ${H.toFixed(1)})`;
}
case "lab": {
const rgb = color.toFormat("rgb");
const r = rgb.getChannelValue("red") / 255;
const g = rgb.getChannelValue("green") / 255;
const b = rgb.getChannelValue("blue") / 255;
const alpha = rgb.getChannelValue("alpha");
const [x, y, z] = rgbToXyz(r, g, b);
const [L, a, b_lab] = xyzToLab(x, y, z);
if (alpha < 1) {
return `lab(${L.toFixed(1)}% ${a.toFixed(1)} ${b_lab.toFixed(
1,
)} / ${alpha.toFixed(2)})`;
}
return `lab(${L.toFixed(1)}% ${a.toFixed(1)} ${b_lab.toFixed(1)})`;
}
default:
return color.toString("hex");
}
}
/**
* Parses a color string in the specified format
*/
export function parseColorFromFormat(
value: string,
format: ColorFormat,
): Color | null {
try {
// For formats that react-aria-components supports directly
if (format === "hex" || format === "rgb" || format === "hsl") {
return parseColor(value);
}
if (format === "hsv") {
// Try to parse HSV/HSB format
const hsvMatch = value.match(/hsva?\(([^)]+)\)/);
if (hsvMatch) {
const parts = hsvMatch[1].split(",").map((p) => p.trim());
const h = parseFloat(parts[0]) || 0;
const s = parseFloat(parts[1]) || 0;
const v = parseFloat(parts[2]) || 0;
const a = parts[3] ? parseFloat(parts[3]) : 1;
// Convert HSV to HSL for react-aria-components
const hslL = (v * (2 - s / 100)) / 2;
const hslS = (v * s) / (100 - Math.abs(2 * hslL - 100));
return parseColor(`hsla(${h}, ${hslS || 0}%, ${hslL}%, ${a})`);
}
}
if (format === "oklch") {
// Parse OKLCH and convert to HSL as approximation
const oklchMatch = value.match(/oklch\(([^)]+)\)/);
if (oklchMatch) {
const parts = oklchMatch[1].split(/[\s\/]+/);
const L = parseFloat(parts[0]) || 50;
const C = parseFloat(parts[1]) || 0;
const H = parseFloat(parts[2]) || 0;
const alpha = parts[3] ? parseFloat(parts[3]) : 1;
// Simplified conversion back to HSL
return parseColor(
`hsla(${H}, ${Math.min(C * 100, 100)}%, ${L}%, ${alpha})`,
);
}
}
if (format === "lab") {
// Parse LAB and convert to HSL as approximation
const labMatch = value.match(/lab\(([^)]+)\)/);
if (labMatch) {
const parts = labMatch[1].split(/[\s\/]+/);
const L = parseFloat(parts[0]) || 50;
const a = parseFloat(parts[1]) || 0;
const b = parseFloat(parts[2]) || 0;
const alpha = parts[3] ? parseFloat(parts[3]) : 1;
// Simplified conversion back to HSL
const chroma = Math.sqrt(a * a + b * b);
const hue = (Math.atan2(b, a) * 180) / Math.PI;
return parseColor(
`hsla(${hue < 0 ? hue + 360 : hue}, ${Math.min(
chroma,
100,
)}%, ${L}%, ${alpha})`,
);
}
}
// Fallback: try to parse as any supported format
return parseColor(value);
} catch {
return null;
}
}
/**
* Validates if a color string is valid for the given format
*/
export function isValidColorFormat(
value: string,
format: ColorFormat,
): boolean {
const parsed = parseColorFromFormat(value, format);
return parsed !== null;
}
/**
* Gets format-specific input placeholder text
*/
export function getFormatPlaceholder(format: ColorFormat): string {
switch (format) {
case "hex":
return "#3b82f6";
case "rgb":
return "rgb(59, 130, 246)";
case "hsl":
return "hsl(220, 91%, 64%)";
case "hsv":
return "hsv(220, 76%, 96%)";
case "oklch":
return "oklch(65% 0.15 230)";
case "lab":
return "lab(55% -10 40)";
default:
return "";
}
}
npx hextaui@latest add color-picker
pnpm dlx hextaui@latest add color-picker
yarn dlx hextaui@latest add color-picker
bun x hextaui@latest add color-picker
Usage
import { ColorPickerWithPresets } from "@/components/ui/ColorPicker";
<ColorPickerWithPresets
defaultValue="#3b82f6"
onChange={(color) => console.log("Selected color:", color)}
/>
Examples
With Custom Presets
<ColorPickerWithPresets
defaultValue="#8b5cf6"
presets={[
"#ef4444",
"#f97316",
"#eab308",
"#22c55e",
"#06b6d4",
"#3b82f6",
"#8b5cf6",
"#e11d48",
]}
/>
Controlled
Current color: #14b8a6
export function ControlledExample() {
const [color, setColor] = React.useState("#14b8a6");
return (
<div className="space-y-4">
<ColorPickerWithPresets value={color} onChange={setColor} />
<p className="text-sm text-muted-foreground">Current color: {color}</p>
</div>
);
}
Color Swatches Only
import { parseColor } from "react-aria-components";
<ColorPicker defaultValue={parseColor("#ef4444")}>
<div className="space-y-3">
<ColorLabel>Choose a color</ColorLabel>
<ColorSwatchPicker>
{colors.map((color) => (
<ColorSwatchPickerItem key={color} color={parseColor(color)}>
<ColorSwatch />
</ColorSwatchPickerItem>
))}
</ColorSwatchPicker>
</div>
</ColorPicker>
Multiple Color Formats
The enhanced color picker supports multiple color formats including HEX, RGB, HSL, HSV, OKLCH, and LAB.
Color Picker with Format Selection
Current color: #3b82f6
import { ColorPickerWithFormats } from "@/components/ui/ColorPicker";
const [color, setColor] = React.useState("#3b82f6");
<ColorPickerWithFormats
value={color}
onChange={setColor}
defaultFormat="hex"
showFormatSelector={true}
showEyeDropper={true}
/>
Props
ColorPickerWithPresets
Prop | Type | Default |
---|---|---|
className? | string | undefined |
size? | "sm" | "default" | "lg" | "default" |
showInput? | boolean | true |
showEyeDropper? | boolean | true |
presets? | string[] | defaultPresets |
onChange? | (color: string) => void | undefined |
defaultValue? | string | "#000000" |
value? | string | undefined |
ColorPickerWithFormats
Prop | Type | Default |
---|---|---|
className? | string | undefined |
size? | "sm" | "default" | "lg" | "default" |
defaultFormat? | "hex" | "rgb" | "hsl" | "hsv" | "oklch" | "lab" | "hex" |
showFormatSelector? | boolean | true |
showEyeDropper? | boolean | true |
presets? | string[] | defaultPresets |
onChange? | (color: string) => void | undefined |
defaultValue? | string | "#000000" |
value? | string | undefined |
Edit on GitHub
Last updated on