Color Picker
A color selection component that allows users to pick a color using a saturation/brightness area, channel sliders, and preset swatches.
import { ColorPicker } from "~/components/color-picker";
export default function ColorPickerBasicDemo() {
return <ColorPicker label="Color" presets={["#ff0000", "#00ff00", "#0000ff"]} />;
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add color-pickerManual
Create the recipe file at src/components/recipes/color-picker.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const colorPickerVariants = tv({
slots: {
root: "flex flex-col gap-1.5",
control: "flex items-center gap-2",
trigger: [
"inline-flex items-center justify-center rounded-md border border-input transition-colors",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
"disabled:cursor-not-allowed disabled:opacity-50",
],
content: [
"z-50 w-64 flex flex-col gap-3 rounded-lg border bg-popover p-4 shadow-md outline-none",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
],
area: "relative h-40 w-full overflow-hidden rounded-md",
areaThumb:
"absolute -translate-x-1/2 -translate-y-1/2 h-4 w-4 rounded-full border-2 border-white shadow-md",
channelSlider: "relative h-3 w-full rounded-full",
channelSliderTrack: "h-full w-full rounded-full",
channelSliderThumb:
"absolute -translate-x-1/2 -translate-y-1/2 h-3 w-3 rounded-full border-2 border-white shadow-md top-1/2",
channelSliderLabel: "text-xs text-muted-foreground",
channelInput: [
"h-8 rounded-md border border-input bg-background px-2.5 py-1.5 text-base md:text-sm text-center ring-offset-background transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
"disabled:cursor-not-allowed disabled:opacity-50",
],
valueSwatch: "h-6 w-6 overflow-hidden rounded-md border",
eyeDropperTrigger: [
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
"hover:bg-accent hover:text-accent-foreground h-7 w-7",
],
formatSelect:
"flex h-7 rounded-md border border-input bg-transparent px-1 py-1 text-xs shadow-sm",
formatTrigger: [
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
"hover:bg-accent hover:text-accent-foreground h-7 w-7",
],
swatchGroup: "flex flex-wrap gap-1",
swatch: "h-6 w-6 overflow-hidden rounded-md border cursor-pointer",
areaBackground: "h-full w-full rounded-[inherit]",
transparencyGrid: "h-full w-full rounded-[inherit]",
view: "flex flex-col gap-3",
valueText: "text-xs text-muted-foreground",
swatchIndicator: "absolute inset-0 flex items-center justify-center",
},
variants: {
size: {
sm: { trigger: "h-8 w-8" },
md: { trigger: "h-8 w-8" },
lg: { trigger: "h-8 w-8" },
},
inline: {
true: { root: "relative" },
},
},
defaultVariants: {
size: "md",
},
});
export type ColorPickerVariants = VariantProps<typeof colorPickerVariants>;Create the component directory and files.
src/components/color-picker/color-picker.base.tsx:
import { ColorPicker as ArkColorPicker } from "@ark-ui/solid/color-picker";
import { splitProps, type Component } from "solid-js";
import { colorPickerVariants, labelVariants } from "../recipes/color-picker";
const styles = colorPickerVariants();
// ── Root (styled, with size + inline variants) ──────────────
const Root: Component<ArkColorPicker.RootProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Root class={styles.root({ class: local.class })} {...others} />;
};
const RootProvider = ArkColorPicker.RootProvider;
// ── Label ────────────────────────────────────────────────────
const Label: Component<ArkColorPicker.LabelProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Label class={labelVariants({ class: local.class })} {...others} />;
};
// ── Control ──────────────────────────────────────────────────
const Control: Component<ArkColorPicker.ControlProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Control class={styles.control({ class: local.class })} {...others} />;
};
// ── Trigger ──────────────────────────────────────────────────
const Trigger: Component<ArkColorPicker.TriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Trigger class={styles.trigger({ class: local.class })} {...others} />;
};
// ── Hidden Input (pass-through) ─────────────────────────────
const HiddenInput = ArkColorPicker.HiddenInput;
// ── Positioner (pass-through) ───────────────────────────────
const Positioner = ArkColorPicker.Positioner;
// ── Content ─────────────────────────────────────────────────
const Content: Component<ArkColorPicker.ContentProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Content class={styles.content({ class: local.class })} {...others} />;
};
// ── Area ─────────────────────────────────────────────────────
const Area: Component<ArkColorPicker.AreaProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Area class={styles.area({ class: local.class })} {...others} />;
};
// ── Area Background ───────────────────────────────────────────
const AreaBackground: Component<ArkColorPicker.AreaBackgroundProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.AreaBackground
class={styles.areaBackground({ class: local.class })}
{...others}
/>
);
};
// ── Area Thumb ───────────────────────────────────────────────
const AreaThumb: Component<ArkColorPicker.AreaThumbProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.AreaThumb class={styles.areaThumb({ class: local.class })} {...others} />;
};
// ── Channel Slider ───────────────────────────────────────────
const ChannelSlider: Component<ArkColorPicker.ChannelSliderProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ChannelSlider
class={styles.channelSlider({ class: local.class })}
{...others}
/>
);
};
// ── Channel Slider Track ─────────────────────────────────────
const ChannelSliderTrack: Component<ArkColorPicker.ChannelSliderTrackProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ChannelSliderTrack
class={styles.channelSliderTrack({ class: local.class })}
{...others}
/>
);
};
// ── Channel Slider Thumb ─────────────────────────────────────
const ChannelSliderThumb: Component<ArkColorPicker.ChannelSliderThumbProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ChannelSliderThumb
class={styles.channelSliderThumb({ class: local.class })}
{...others}
/>
);
};
// ── Channel Slider Label ─────────────────────────────────────
const ChannelSliderLabel: Component<ArkColorPicker.ChannelSliderLabelProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ChannelSliderLabel
class={styles.channelSliderLabel({ class: local.class })}
{...others}
/>
);
};
// ── Channel Slider Value Text (pass-through) ────────────────
const ChannelSliderValueText = ArkColorPicker.ChannelSliderValueText;
// ── Channel Input ────────────────────────────────────────────
const ChannelInput: Component<ArkColorPicker.ChannelInputProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ChannelInput class={styles.channelInput({ class: local.class })} {...others} />
);
};
// ── Value Swatch ─────────────────────────────────────────────
const ValueSwatch: Component<ArkColorPicker.ValueSwatchProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.ValueSwatch class={styles.valueSwatch({ class: local.class })} {...others} />
);
};
// ── Value Text (pass-through) ───────────────────────────────
const ValueText = ArkColorPicker.ValueText;
// ── Eye Dropper Trigger ─────────────────────────────────────
const EyeDropperTrigger: Component<ArkColorPicker.EyeDropperTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.EyeDropperTrigger
class={styles.eyeDropperTrigger({ class: local.class })}
{...others}
/>
);
};
// ── Format Select ────────────────────────────────────────────
const FormatSelect: Component<ArkColorPicker.FormatSelectProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.FormatSelect class={styles.formatSelect({ class: local.class })} {...others} />
);
};
// ── Format Trigger ───────────────────────────────────────────
const FormatTrigger: Component<ArkColorPicker.FormatTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.FormatTrigger
class={styles.formatTrigger({ class: local.class })}
{...others}
/>
);
};
// ── Swatch Group ─────────────────────────────────────────────
const SwatchGroup: Component<ArkColorPicker.SwatchGroupProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.SwatchGroup class={styles.swatchGroup({ class: local.class })} {...others} />
);
};
// ── Swatch ───────────────────────────────────────────────────
const Swatch: Component<ArkColorPicker.SwatchProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.Swatch class={styles.swatch({ class: local.class })} {...others} />;
};
// ── Swatch Indicator ─────────────────────────────────────────
const SwatchIndicator: Component<ArkColorPicker.SwatchIndicatorProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.SwatchIndicator
class={styles.swatchIndicator({ class: local.class })}
{...others}
/>
);
};
// ── Swatch Trigger ───────────────────────────────────────────
const SwatchTrigger: Component<ArkColorPicker.SwatchTriggerProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.SwatchTrigger class={styles.swatch({ class: local.class })} {...others} />;
};
// ── Transparency Grid ────────────────────────────────────────
const TransparencyGrid: Component<ArkColorPicker.TransparencyGridProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkColorPicker.TransparencyGrid
class={styles.transparencyGrid({ class: local.class })}
{...others}
/>
);
};
// ── View ─────────────────────────────────────────────────────
const View: Component<ArkColorPicker.ViewProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkColorPicker.View class={styles.view({ class: local.class })} {...others} />;
};
// ── Context (pass-through) ──────────────────────────────────
const Context = ArkColorPicker.Context;
// ── Namespace Export ────────────────────────────────────────
export const ColorPickerBase = {
Root,
RootProvider,
Label,
Control,
Trigger,
HiddenInput,
Positioner,
Content,
Area,
AreaBackground,
AreaThumb,
ChannelSlider,
ChannelSliderTrack,
ChannelSliderThumb,
ChannelSliderLabel,
ChannelSliderValueText,
ChannelInput,
ValueSwatch,
ValueText,
EyeDropperTrigger,
FormatSelect,
FormatTrigger,
SwatchGroup,
Swatch,
SwatchIndicator,
SwatchTrigger,
TransparencyGrid,
View,
Context,
};src/components/color-picker/index.tsx:
import { For, Show, splitProps, type Component, type JSX } from "solid-js";
import { ColorPicker as ArkColorPicker, parseColor } from "@ark-ui/solid/color-picker";
import { Portal } from "solid-js/web";
import { ColorPickerBase } from "./color-picker.base";
const CheckIcon: Component<JSX.SvgSVGAttributes<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
{...props}
>
<path d="M20 6L9 17l-5-5" />
<title>Check</title>
</svg>
);
// ── Composite: ColorPicker ───────────────────────────────────
type ColorPickerProps = ArkColorPicker.RootProps & {
label?: string;
presets?: string[];
inline?: boolean;
};
const PickerContent: Component<{ presets?: string[]; children?: JSX.Element }> = (props) => {
const presetColors = () => props.presets?.map((c) => parseColor(c));
return (
<ColorPickerBase.Content>
<ColorPickerBase.Area>
<ColorPickerBase.AreaBackground />
<ColorPickerBase.AreaThumb />
</ColorPickerBase.Area>
<ColorPickerBase.ChannelSlider channel="hue">
<ColorPickerBase.ChannelSliderTrack />
<ColorPickerBase.ChannelSliderThumb />
</ColorPickerBase.ChannelSlider>
<ColorPickerBase.FormatSelect />
<ColorPickerBase.ChannelInput channel="hex" />
<Show when={presetColors()}>
<ColorPickerBase.SwatchGroup>
<For each={presetColors()}>
{(color) => (
<ColorPickerBase.SwatchTrigger value={color}>
<ColorPickerBase.Swatch value={color}>
<ColorPickerBase.SwatchIndicator>
<CheckIcon />
</ColorPickerBase.SwatchIndicator>
</ColorPickerBase.Swatch>
</ColorPickerBase.SwatchTrigger>
)}
</For>
</ColorPickerBase.SwatchGroup>
</Show>
{props.children}
</ColorPickerBase.Content>
);
};
const ColorPicker: Component<ColorPickerProps> = (props) => {
const [local, others] = splitProps(props, ["class", "label", "presets", "inline", "children"]);
return (
<ColorPickerBase.Root class={local.class} {...others}>
<Show when={local.label}>
<ColorPickerBase.Label>{local.label}</ColorPickerBase.Label>
</Show>
<ColorPickerBase.Control>
<ColorPickerBase.ChannelInput channel="hex" />
<ColorPickerBase.Trigger>
<ColorPickerBase.TransparencyGrid />
<ColorPickerBase.ValueSwatch />
</ColorPickerBase.Trigger>
</ColorPickerBase.Control>
<Show
when={local.inline}
fallback={
<Portal>
<ColorPickerBase.Positioner>
<PickerContent presets={local.presets}>{local.children}</PickerContent>
</ColorPickerBase.Positioner>
</Portal>
}
>
<PickerContent presets={local.presets}>{local.children}</PickerContent>
</Show>
<ColorPickerBase.HiddenInput />
</ColorPickerBase.Root>
);
};
// ── Exports ──────────────────────────────────────────────────
export { ColorPicker, ColorPickerBase };
export { colorPickerVariants, type ColorPickerVariants } from "../recipes/color-picker";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Usage
Basic Usage
import { ColorPicker } from "~/components/color-picker";
export default function ColorPickerBasicDemo() {
return <ColorPicker label="Color" presets={["#ff0000", "#00ff00", "#0000ff"]} />;
}Inline
For inline rendering (no popover), use the inline prop:
import { ColorPicker } from "~/components/color-picker";
import { parseColor } from "@ark-ui/solid/color-picker";
export default function ColorPickerInlineDemo() {
return (
<div>
<ColorPicker
inline
label="Inline Color Picker"
defaultValue={parseColor("#eb5e41")}
presets={["#ff0000", "#00ff00", "#0000ff"]}
/>
</div>
);
}Controlled
Current color: rgba(235, 94, 65, 1)
import { createSignal } from "solid-js";
import { ColorPicker } from "~/components/color-picker";
import { parseColor } from "@ark-ui/solid/color-picker";
export default function ColorPickerControlledDemo() {
const [color, setColor] = createSignal(parseColor("#eb5e41"));
return (
<div>
<ColorPicker
label="Color"
value={color()}
onValueChange={(e) => setColor(e.value)}
presets={["#ff0000", "#00ff00", "#0000ff"]}
/>
<p class="text-sm text-muted-foreground mt-3">
Current color: <span class="font-mono text-foreground">{color().toString()}</span>
</p>
</div>
);
}Advanced Usage
For advanced layouts with custom composition, import ColorPickerBase to access individual parts:
import { Portal } from "solid-js/web";
import { parseColor } from "@ark-ui/solid/color-picker";
import { ColorPickerBase } from "~/components/color-picker";
export default function ColorPickerAdvancedDemo() {
return (
<ColorPickerBase.Root defaultValue={parseColor("#eb5e41")}>
<ColorPickerBase.Label>Custom Color Picker</ColorPickerBase.Label>
<ColorPickerBase.Control>
{/* Custom trigger setup */}
<ColorPickerBase.Trigger>
<ColorPickerBase.TransparencyGrid />
<ColorPickerBase.ValueSwatch />
</ColorPickerBase.Trigger>
<ColorPickerBase.ChannelInput channel="hex" />
</ColorPickerBase.Control>
<Portal>
<ColorPickerBase.Positioner>
<ColorPickerBase.Content>
<ColorPickerBase.Area>
<ColorPickerBase.AreaBackground />
<ColorPickerBase.AreaThumb />
</ColorPickerBase.Area>
<div class="flex flex-col gap-3 mt-3">
<ColorPickerBase.ChannelSlider channel="hue">
<ColorPickerBase.ChannelSliderTrack />
<ColorPickerBase.ChannelSliderThumb />
</ColorPickerBase.ChannelSlider>
<ColorPickerBase.ChannelSlider channel="alpha">
<ColorPickerBase.TransparencyGrid />
<ColorPickerBase.ChannelSliderTrack />
<ColorPickerBase.ChannelSliderThumb />
</ColorPickerBase.ChannelSlider>
</div>
</ColorPickerBase.Content>
</ColorPickerBase.Positioner>
</Portal>
<ColorPickerBase.HiddenInput />
</ColorPickerBase.Root>
);
}API Reference
See the Ark UI Color Picker documentation.