Build Color Area Picker
Let's build a 2D color area picker step by step.
Here's what we'll end up with:
Click to view the full code
import "internationalized-color/css";
import { ColorArea, useColor } from "@urcolor/react";
export default function ColorAreaGuide() {
const { color, setColor } = useColor("hsl(210, 80%, 50%)");
return (
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelX="h"
channelY="s"
className="block"
>
<ColorArea.Track
className="
relative h-[200px] w-full cursor-crosshair touch-none overflow-clip
rounded-lg
"
>
<ColorArea.Gradient className="absolute inset-0" />
<ColorArea.Thumb
className="
absolute size-5
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)]
"
/>
</ColorArea.Track>
</ColorArea.Root>
);
}Step 1: Set up state
Start by importing the color hook and creating color state.
import { useColor } from "@urcolor/react";
function MyArea() {
const { color, setColor } = useColor("hsl(210, 80%, 50%)");
}Step 2: Add the root
ColorArea.Root manages all the state and interactions. Tell it which color space and channels to use for each axis.
import { useColor, ColorArea } from "@urcolor/react";
function MyArea() {
const { color, setColor } = useColor("hsl(210, 80%, 50%)");
return (
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelX="h"
channelY="s"
>
{/* children go here */}
</ColorArea.Root>
);
}colorSpace— the color space to work in (hsl,oklch,hsb, etc.)channelX— the channel mapped to the horizontal axischannelY— the channel mapped to the vertical axis
Step 3: Add the track and gradient
ColorArea.Track is the interactive area that handles pointer events. ColorArea.Gradient renders the 2D gradient on a canvas.
import { useColor, ColorArea } from "@urcolor/react";
function MyArea() {
const { color, setColor } = useColor("hsl(210, 80%, 50%)");
return (
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelX="h"
channelY="s"
>
<ColorArea.Track
className="
relative h-[200px] w-full cursor-crosshair
touch-none overflow-clip rounded-lg
"
>
<ColorArea.Gradient className="absolute inset-0" />
</ColorArea.Track>
</ColorArea.Root>
);
}The track needs a fixed height and position: relative so the thumb can be positioned inside it. touch-none prevents scroll interference on mobile.
Step 4: Add the thumb
ColorArea.Thumb is the draggable handle. It's positioned automatically by the component.
import { useColor, ColorArea } from "@urcolor/react";
function MyArea() {
const { color, setColor } = useColor("hsl(210, 80%, 50%)");
return (
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelX="h"
channelY="s"
>
<ColorArea.Track
className="
relative h-[200px] w-full cursor-crosshair
touch-none overflow-clip rounded-lg
"
>
<ColorArea.Gradient className="absolute inset-0" />
<ColorArea.Thumb
className="
absolute size-5
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)]
"
/>
</ColorArea.Track>
</ColorArea.Root>
);
}TIP
All components are completely unstyled — the classes above are just an example using Tailwind CSS. Use any styling approach you prefer.
Switching color spaces
Change the color space and channel mapping for different picker behavior. For example, OKLCh:
const { color, setColor } = useColor("oklch(0.6, 0.15, 210)");
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="oklch"
channelX="hue"
channelY="chroma"
>
{/* ... */}
</ColorArea.Root>Inverting axis direction
Use invertedX or invertedY to reverse axis directions:
<ColorArea.Root
value={color}
onValueChange={setColor}
colorSpace="hsl"
channelX="h"
channelY="l"
invertedX
invertedY
>
{/* ... */}
</ColorArea.Root>Listening to changes
Use onValueChange for real-time updates (while dragging) and onValueCommit for the final value (on release):
import { Color } from "internationalized-color";
const onColorChange = (color: Color) => {
console.log("dragging", color.toString("css"));
};
const onColorCommit = (color: Color) => {
console.log("committed", color.toString("css"));
};
<ColorArea.Root
value={color}
onValueChange={onColorChange}
onValueCommit={onColorCommit}
colorSpace="hsl"
channelX="h"
channelY="s"
>
{/* ... */}
</ColorArea.Root>