title: "color-zone — A React Color Picker with Gradient Support, EyeDropper, and a Headless Hook"
published: true
tags: react, typescript, opensource, webdev
description: "A zero-dependency React color picker supporting HSV, RGB, HSL, HEX, linear/radial gradients, EyeDropper API, and a headless useColorPicker hook."
I built a React color picker that actually handles gradients
Most color picker libraries handle solid colors well. As soon as you need gradient support — letting users build linear-gradient or radial-gradient values with multiple stops — you're either stitching together multiple libraries or building it yourself.
I needed exactly that for a project, so I built color-zone.
What it does
- HSV picker square with hue and alpha sliders
- Switchable input modes: HEX, RGB, HSL
- Full gradient editing — linear and radial, with draggable multi-stop bar
- EyeDropper API for sampling any color on screen
- A
useColorPickerhook for headless / custom UIs - Zero runtime dependencies
- Full TypeScript support
Install
npm install color-zone
Basic usage
import { ColorPicker } from "color-zone";
import { useState } from "react";
export default function App() {
const [color, setColor] = useState("#3498db");
return <ColorPicker value={color} onChange={setColor} />;
}
That's it. The picker is a controlled component — you own the state, it calls onChange on every update.
Gradient mode is automatic
Pass a CSS gradient string and the picker switches into gradient mode — no extra config needed.
const [bg, setBg] = useState(
"linear-gradient(90deg, #e52d27 0%, #b31217 100%)"
);
<ColorPicker value={bg} onChange={setBg} />
Users get a full stop bar: click to add a stop, drag to reposition, double-click to remove. The onChange callback returns a valid CSS gradient string you can apply directly to background.
The headless hook
If you want full control over the UI, skip the component and use useColorPicker instead.
import { useColorPicker } from "color-zone";
function CustomPicker() {
const [color, setColor] = useState("#e74c3c");
const {
valueToHex,
valueToHSL,
valueToCmyk,
setR, setG, setB, setA,
setHue, setSaturation, setLightness,
setLinear, setRadial, setDegrees,
addPoint, deletePoint, setSelectedPoint,
} = useColorPicker(color, setColor);
return (
<div>
<p>HEX: {valueToHex()}</p>
<p>HSL: {valueToHSL()}</p>
<p>CMYK: {valueToCmyk()}</p>
<button onClick={() => setLinear()}>Make Linear Gradient</button>
<button onClick={() => setRadial()}>Make Radial Gradient</button>
</div>
);
}
Color utilities are exported too
import {
rgb2hsl, rgb2hsv, rgb2cmyk,
hsv2rgb, toHex, toHexA,
parseColor, parseGradient, buildGradientString,
} from "color-zone";
const [h, s, l] = rgb2hsl(255, 99, 71);
// → [9, 100, 64]
const parsed = parseColor("rgba(255, 99, 71, 0.8)");
// → { r: 255, g: 99, b: 71, a: 0.8 }
Hiding what you don't need
// Minimal — hue + alpha sliders only
<ColorPicker
value={color}
onChange={setColor}
hidePickerSquare
hideInputs
hideEyeDrop
/>
TypeScript
import type {
ColorPickerProps,
UseColorPickerReturn,
GradientStop,
ParsedGradient,
} from "color-zone";
EyeDropper
On Chrome/Edge 95+, a dropper button appears automatically — no configuration needed. Silently hidden everywhere else.
Links
Let me know if you run into issues or have feature requests — happy to take them in the issues tab.

Top comments (0)