ColorWheel
A circular 2D area component for adjusting two color channels mapped to angle and radius.
Preview
Source code
import "internationalized-color/css";
import { ColorWheel, useColor } from "@urcolor/react";
export default function ColorWheelHS() {
const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");
return (
<>
<code>{hex}</code>
<ColorWheel.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelAngle="h"
channelRadius="s"
className="relative block size-64 overflow-hidden rounded-full"
style={{ containerType: "inline-size" }}
>
<ColorWheel.Gradient className="absolute inset-0 block" />
<ColorWheel.Thumb
className="
size-4 rounded-full border-2 border-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
/>
</ColorWheel.Root>
</>
);
}Anatomy
<ColorWheel.Root>
<ColorWheel.Checkerboard />
<ColorWheel.Gradient />
<ColorWheel.Thumb />
</ColorWheel.Root>Examples
HSL / Hue x Saturation
HSL color wheel with Hue mapped to angle and Saturation to radius.
Source code
import "internationalized-color/css";
import { ColorWheel, useColor } from "@urcolor/react";
export default function ColorWheelHS() {
const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");
return (
<>
<code>{hex}</code>
<ColorWheel.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelAngle="h"
channelRadius="s"
className="relative block size-64 overflow-hidden rounded-full"
style={{ containerType: "inline-size" }}
>
<ColorWheel.Gradient className="absolute inset-0 block" />
<ColorWheel.Thumb
className="
size-4 rounded-full border-2 border-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
/>
</ColorWheel.Root>
</>
);
}HSL / Hue x Lightness
HSL color wheel with Hue mapped to angle and Lightness to radius.
Source code
import "internationalized-color/css";
import { ColorWheel, useColor } from "@urcolor/react";
export default function ColorWheelHL() {
const { color, setColor, hex } = useColor("hsl(210, 80%, 50%)");
return (
<>
<code>{hex}</code>
<ColorWheel.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelAngle="h"
channelRadius="l"
className="relative block size-64 overflow-hidden rounded-full"
style={{ containerType: "inline-size" }}
>
<ColorWheel.Gradient className="absolute inset-0 block" />
<ColorWheel.Thumb
className="
size-4 rounded-full border-2 border-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
/>
</ColorWheel.Root>
</>
);
}OKLCh / Hue x Chroma
OKLCh color wheel with Hue mapped to angle and Chroma to radius.
Source code
import "internationalized-color/css";
import { ColorWheel, useColor } from "@urcolor/react";
export default function ColorWheelOKLCh() {
const { color, setColor, hex } = useColor("oklch(0.6 0.15 210)");
return (
<>
<code>{hex}</code>
<ColorWheel.Root
value={color}
onValueChange={setColor}
colorSpace="oklch"
channelAngle="h"
channelRadius="c"
className="relative block size-64 overflow-hidden rounded-full"
style={{ containerType: "inline-size" }}
>
<ColorWheel.Gradient className="absolute inset-0 block" />
<ColorWheel.Thumb
className="
size-4 rounded-full border-2 border-white
shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
"
/>
</ColorWheel.Root>
</>
);
}API Reference
ColorWheel.Root
The root container that manages wheel state and color channel binding.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Color | string | null | — | Controlled color value. |
defaultValue | Color | string | 'hsl(0, 100%, 50%)' | Initial color when uncontrolled. |
colorSpace | string | 'hsl' | Color space mode (e.g. 'hsl', 'oklch'). |
channelAngle | string | Auto | Channel mapped to the angle axis. Auto-derived from color space. |
channelRadius | string | Auto | Channel mapped to the radius axis. Auto-derived from color space. |
startAngle | number | 0 | Starting angle offset in degrees. |
disabled | boolean | false | Disables interaction. |
onValueChange | (color: Color) => void | — | Called when color changes. |
onValueCommit | (color: Color) => void | — | Called when interaction ends. |
ColorWheel.Gradient
Renders a polar gradient canvas for the wheel.
| Prop | Type | Default | Description |
|---|---|---|---|
channelOverrides | Record<string, number> | false | { alpha: 1 } | Lock specific channels to fixed values in the gradient. |
ColorWheel.Checkerboard
Renders a checkerboard pattern behind the gradient to visualize alpha transparency.
ColorWheel.Thumb
Wrapper for the thumb indicator. Position is set automatically using polar coordinates.
ColorWheel.ThumbX / ColorWheel.ThumbY
Individual axis thumb elements for angle and radius keyboard navigation.
Accessibility
ColorWheel provides a circular 2D interface with two independently focusable thumb elements for keyboard access to the angle and radius axes.
Keyboard Navigation
| Key | Action |
|---|---|
| Arrow Right | Increase angle by one step |
| Arrow Left | Decrease angle by one step |
| Arrow Up | Increase radius by one step |
| Arrow Down | Decrease radius by one step |
| Shift + Arrow | Move by 10 steps |
| Page Up / Page Down | Increase/decrease radius by 10 steps |
| Home | Move to minimum |
| End | Move to maximum |