Color and Gradient Picker for React and Vue
Solid colors, linear, radial and conic gradients, your own swatch groups and an eyedropper. It takes plain CSS strings and hands them back.
MIT licensed · v0.1.0 · React 18+ and Vue 3
linear-gradient(135deg, #3E5CEB 0%, #A855F7 52%, #F97316 100%)A React color picker with every gradient type built in
Linear, radial, conic and repeating gradients, your brand colors, trackpad gestures and a contrast check, in one component for React or Vue. Every card below is live.
Linear, radial, conic and repeating
Edit every CSS gradient type, with angle, center, shape and as many stops as you like.
Two-finger swipe
On a trackpad, swipe to move the color area, sliders, stops and angle. The page still scrolls everywhere else.
Stops that move like objects
Swipe a stop along the bar, or use the gestures you know from design tools.
Gradient handles on your canvas
Draw start and end handles right on the design, like Figma. Two helper functions give your editor the same handles.
gradientHandles(value, width, height)Paste any CSS color
Hex, rgb, hsl, oklch, named or display-p3. Press Cmd+V in the picker.
Contrast as you pick
A WCAG badge against your background. For gradients it checks the weakest stop.
Brand and saved colors
Your own swatch groups, with add, remove, rename, reorder and search.
- Never crashes on bad dataUnknown or broken values show as unsupported and stay untouched.
- Keyboard and screen readerEvery control works by keyboard, is labelled and supports right to left.
- Light, dark and your themeMatch your app with a few --cs-* CSS variables.
- Undo, eyedropper, copy and pasteCmd+Z inside the picker, a screen eyedropper, Cmd+C and Cmd+V.
Play with Colorshot live, select a layer
This is the real Orshot Studio, embedded on this page. Click any color in the panel to open Colorshot. Nothing you change is saved.
Select a shape, then click its fill color to open Colorshot.
Add it to your app in a few minutes
Install
One package. Your app only bundles the entry it imports, and React and Vue are optional peers.
npm i @orshot/colorshotpnpm add @orshot/colorshotyarn add @orshot/colorshotbun add @orshot/colorshot| Import | What it is |
|---|---|
| @orshot/colorshot/react | React components and hooks |
| @orshot/colorshot/vue | Vue components and composables |
| @orshot/colorshot | Color math, CSS gradient parsing and the picker store. Safe in server code |
| @orshot/colorshot/styles.css | The stylesheet, imported once |
Quick start
value is any CSS color or gradient. onChange fires while dragging, onChangeComplete once when the change is done.
"use client";
import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
import "@orshot/colorshot/styles.css";
export function FillPicker() {
const [fill, setFill] = useState("linear-gradient(135deg, #3E5CEB 0%, #22C55E 100%)");
return (
<ColorPicker
value={fill}
onChange={setFill} // every frame while dragging
onChangeComplete={(value) => save(value)} // once, when the change is done
/>
);
}<script setup>
import { ref } from "vue";
import { ColorPicker } from "@orshot/colorshot/vue";
import "@orshot/colorshot/styles.css";
const fill = ref("#3E5CEB");
</script>
<template>
<ColorPicker v-model="fill" @change-complete="save" />
</template>import { createPicker, parseColor, formatColor } from "@orshot/colorshot";
// The same store the React and Vue pickers use, safe in server code too
const picker = createPicker({
value: "#3E5CEB",
onChange: (value) => console.log(value),
});
picker.setHsva({ h: 140 });
picker.commit();
formatColor(parseColor("rgb(62 92 235 / 50%)").color, "hex"); // "#3e5ceb80"With a coding agent
Paste this into Claude Code, Cursor or Codex. It installs Colorshot and swaps your pickers after you confirm the list.
Add Colorshot (@orshot/colorshot), an open source color and gradient picker, to this project.
1. Install it with this project's package manager: npm i @orshot/colorshot
2. Import the stylesheet once, in the app's root layout or entry file: import "@orshot/colorshot/styles.css"
3. Use the entry for this project's framework:
- React: import { ColorPicker, ColorField } from "@orshot/colorshot/react"
- Vue: import { ColorPicker, ColorField } from "@orshot/colorshot/vue"
- No framework: import { createPicker } from "@orshot/colorshot"
4. ColorPicker takes and returns plain CSS strings: value is any CSS color or gradient, onChange fires while dragging, onChangeComplete fires once when the change is done (save there).
5. Use ColorField when a field should open the picker in a popover.
6. Pass modes={["solid"]} where gradients are not allowed, and alpha={false} where transparency is not allowed.
7. In Next.js, render the picker from a client component ("use client").
Find the color inputs and pickers in this codebase, show me the list, and replace the ones I confirm. Keep the stored value format the same, and run the existing tests after.
Full API reference: https://orshot.com/open-source/colorshotOr install the Colorshot skill, so your agent knows the API in every session. The full reference for LLMs is in llms.txt.
npx skills add rishimohan/colorshotColorPicker
The ready-made panel: area, sliders, every gradient mode, inputs and swatch groups.
linear-gradient(135deg, #3E5CEB 0%, #F97316 100%)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
export function Example() {
const [value, setValue] = useState("linear-gradient(135deg, #3E5CEB 0%, #F97316 100%)");
return <ColorPicker value={value} onChange={setValue} gradientPresets swatchSearch />;
}ColorField
A field that opens the picker in a popover. It flips and shifts to stay on screen, closes on Escape or an outside click, and returns focus.
Click the field to open the picker
import { useState } from "react";
import { ColorField } from "@orshot/colorshot/react";
export function Example() {
const [fill, setFill] = useState("#3E5CEB");
// takes every ColorPicker prop, plus placement, portal and a custom trigger
return <ColorField label="Fill" value={fill} onChange={setFill} gradientPresets />;
}| Prop | Type | What it does |
|---|---|---|
| label / placeholder | string | Trigger label, and the text shown when the value is empty. |
| placement | bottom-start | bottom-end | top-start | top-end | right-start | left-start | Preferred side. It flips and shifts to stay on screen. |
| open / defaultOpen / onOpenChange | boolean, (open) => void | Control the popover, or leave it uncontrolled. |
| portal | boolean | HTMLElement | Render the popover elsewhere in the DOM. |
| renderTrigger | ({ value, open }) => ReactNode | Your own trigger button. |
| pickerClassName / pickerStyle | string, CSSProperties | Style the picker inside the popover. |
| ...ColorPicker props | Every ColorPicker prop works here too. |
Parts
Build your own layout from the same parts the ready-made picker uses. Picker.Root holds the state; put any parts inside it.
#22C55Eimport { useState } from "react";
import { Picker } from "@orshot/colorshot/react";
// Your own layout: a compact solid picker with only an area, hue and a hex field
export function Example() {
const [value, setValue] = useState("#22C55E");
return (
<Picker.Root value={value} onChange={setValue} modes={["solid"]}>
<Picker.Area />
<Picker.Hue />
<Picker.Inputs formats={["hex"]} alpha={false} />
</Picker.Root>
);
}#3E5CEBimport { useState } from "react";
import { Picker } from "@orshot/colorshot/react";
// A brand palette with no free picking: swatches only
export function Example() {
const [value, setValue] = useState("#3E5CEB");
return (
<Picker.Root value={value} onChange={setValue} modes={["solid"]}>
<Picker.Swatches groups={[{ id: "brand", label: "Brand", colors: BRAND_COLORS }]} />
</Picker.Root>
);
}| Part | What it is |
|---|---|
| Picker.Root | The panel and the state every other part reads. Takes the ColorPicker value props. |
| Picker.Area | The 2D saturation and brightness area. |
| Picker.Hue / Picker.Alpha | The hue and opacity sliders. |
| Picker.ModeTabs | Solid, Linear, Radial and Conic tabs. |
| Picker.GradientEditor | Bar plus controls, shown only in gradient modes. |
| Picker.GradientBar | The stop bar on its own. |
| Picker.GradientControls / Picker.AngleDial / Picker.CenterPad | Angle, shape, size and center controls. |
| Picker.Inputs | Format menu and channel fields (hex, rgb, hsl, ...). |
| Picker.EyeDropper | Screen color picking where the browser supports it. |
| Picker.Swatches | Swatch groups with tabs, search, add and remove. |
| Picker.CurrentSwatch / Picker.Preview | The current value, with an optional before and after. |
| Picker.Contrast | WCAG contrast badge against a background. |
| Picker.Notice | Shown when a value cannot be read. |
Linear gradient
One mode, no tabs. Drag the stops, Alt-drag one to duplicate it, drag it off the bar to remove it. The dial sets the angle.
linear-gradient(90deg, #22C55E 0%, #3E5CEB 100%)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// A linear gradient picker: drag the stops, Alt-drag to duplicate one,
// drag a stop off the bar to remove it. The angle dial sets the direction.
export function Example() {
const [value, setValue] = useState("linear-gradient(90deg, #22C55E 0%, #3E5CEB 100%)");
return <ColorPicker value={value} onChange={setValue} modes={["linear"]} />;
}Radial gradient
The center pad moves the center and switches between circle and ellipse. Size and repeating are in the ... menu.
radial-gradient(circle at 30% 30%, #FDE68A 0%, #F97316 45%, #BE123C 100%)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// A radial gradient picker: drag the center pad to move the center and switch
// between circle and ellipse. The size keyword and Repeating live in the ... menu.
export function Example() {
const [value, setValue] = useState(
"radial-gradient(circle at 30% 30%, #FDE68A 0%, #F97316 45%, #BE123C 100%)",
);
return <ColorPicker value={value} onChange={setValue} modes={["radial"]} />;
}Conic gradient
The dial sets the start angle and the center pad moves the center. Good for color wheels and pie-like fills.
conic-gradient(from 0deg at 50% 50%, #EF4444, #F59E0B, #22C55E, #3B82F6, #A855F7, #EF4444)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// A conic gradient picker: the angle dial sets the start angle,
// the center pad moves the center.
export function Example() {
const [value, setValue] = useState(
"conic-gradient(from 0deg at 50% 50%, #EF4444, #F59E0B, #22C55E, #3B82F6, #A855F7, #EF4444)",
);
return <ColorPicker value={value} onChange={setValue} modes={["conic"]} />;
}Gradients only
For backgrounds that must be a gradient. defaultGradient is what a solid value turns into, and presets give people a place to start.
linear-gradient(135deg, #F472B6 0%, #6366F1 100%)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// Backgrounds that must be a gradient: no Solid tab, preset gradients to start from.
export function Example() {
const [value, setValue] = useState("linear-gradient(135deg, #F472B6 0%, #6366F1 100%)");
return (
<ColorPicker
value={value}
onChange={setValue}
modes={["linear", "radial", "conic"]}
defaultGradient="linear-gradient(90deg, #3E5CEB 0%, #22C55E 100%)"
gradientPresets
/>
);
}Solid colors only
For fields where a gradient or transparency makes no sense, like a brand accent.
#A855F7<ColorPicker
value={value}
onChange={setValue}
modes={["solid"]} // no gradient tabs
alpha={false} // no opacity slider or field
formats={["hex", "rgb", "hsl"]}
/>Text color with contrast
contrastWith shows the WCAG contrast ratio against the background and draws the AA line on the color area.
Body text on a white page
import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// A text color on a white page: solid only, no transparency,
// and a WCAG contrast badge against the background.
export function Example() {
const [value, setValue] = useState("#64748B");
return (
<ColorPicker
value={value}
onChange={setValue}
modes={["solid"]}
alpha={false}
contrastWith="#FFFFFF"
/>
);
}OKLCH and wide gamut
Edit in a perceptual space. The area is drawn in Display P3 where the screen supports it, and a dashed line marks the edge of sRGB.
oklch(0.62 0.2 265)import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// Perceptual editing: the area is OKLCH lightness by chroma, drawn in Display P3
// where the screen supports it. A dashed line marks the edge of sRGB.
export function Example() {
const [value, setValue] = useState("oklch(0.62 0.2 265)");
return (
<ColorPicker
value={value}
onChange={setValue}
modes={["solid"]}
space="oklch"
formats={["oklch", "hex", "rgb"]}
outputFormat="oklch"
/>
);
}Undo and compare
history adds Cmd/Ctrl+Z inside the picker. compare splits the header swatch into before and after.
#F97316import { useState } from "react";
import { ColorPicker } from "@orshot/colorshot/react";
// Cmd/Ctrl+Z and Shift+Cmd/Ctrl+Z inside the picker. The header swatch splits
// into before and after once the value changes; click "before" to restore.
export function Example() {
const [value, setValue] = useState("#F97316");
return <ColorPicker value={value} onChange={setValue} history compare />;
}In a form
ColorField works with any form library. It takes a value and an onChange like an input, and submits a plain CSS string.
"use client";
import { Controller, useForm } from "react-hook-form";
import { ColorField } from "@orshot/colorshot/react";
// A settings form: the field opens the picker in a popover and submits a plain CSS string.
export function BrandForm({ onSubmit }) {
const { control, handleSubmit } = useForm({
defaultValues: { accent: "#3E5CEB", background: "linear-gradient(135deg, #0F172A 0%, #1E293B 100%)" },
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="accent"
control={control}
render={({ field }) => (
<ColorField label="Accent" value={field.value} onChange={field.onChange} modes={["solid"]} alpha={false} />
)}
/>
<Controller
name="background"
control={control}
render={({ field }) => (
<ColorField label="Background" value={field.value} onChange={field.onChange} gradientPresets />
)}
/>
<button type="submit">Save</button>
</form>
);
}Saving changes
onChange fires on every frame of a drag. Update the preview there, and save or record undo in onChangeComplete, which fires once.
// Update the canvas on every frame, save once when the drag ends.
<ColorPicker
value={layer.fill}
onChange={(fill) => updateLayerPreview(layer.id, { fill })}
onChangeComplete={(fill) => saveLayer(layer.id, { fill })}
/>With shadcn/ui
Colorshot drops into a shadcn/ui project as is. Point its variables at your theme tokens and it matches your buttons and inputs, light and dark.
/* globals.css: use your shadcn/ui tokens, so the picker follows your theme and dark mode.
On shadcn/ui v3 (HSL channel tokens) wrap them: hsl(var(--primary)) */
[data-colorshot],
[data-colorshot-field] {
--cs-accent: var(--primary);
--cs-bg: var(--popover);
--cs-fg: var(--popover-foreground);
--cs-border: var(--border);
--cs-radius: var(--radius);
}Swatch groups
Each group becomes a tab. Add onAdd and onRemove to let people save and delete colors.
<ColorPicker
value={value}
onChange={setValue}
swatchSearch
swatches={[
{
id: "brand",
label: "Brand",
colors: [{ value: "#3E5CEB", label: "Tide" }, "#0A0A0A", "#F5F5F5"],
onAdd: (value) => saveBrandColor(value), // shows a + button
onRemove: (swatch) => deleteBrandColor(swatch),
},
{ id: "recent", label: "Recent", recent: true },
]}
/>Output format
By default a picked color keeps the format of the value. Pass outputFormat to write the format your app stores.
import { formatColor } from "@orshot/colorshot";
// Orshot's own setting: uppercase hex when opaque, comma rgba when not.
// Gradients keep their shape; edited stops use the same format.
function outputFormat(color) {
if (color.alpha >= 1) return formatColor({ ...color, alpha: 1 }, "hex", { upper: true });
return formatColor(color, "rgb", { legacy: true, fn: "rgba" });
}
<ColorPicker value={value} onChange={setValue} outputFormat={outputFormat} />Theming
Light and dark are built in. Everything else is a CSS variable on the picker or any parent.
radial-gradient(circle at 30% 30%, #FDE68A 0%, #F97316 45%, #BE123C 100%)<ColorPicker
value={value}
onChange={setValue}
theme="dark"
style={{
"--cs-accent": "#E11D48",
"--cs-radius": "20px",
"--cs-radius-control": "10px",
}}
/>/* any parent, or the picker itself */
.my-panel [data-colorshot] {
--cs-accent: #3e5ceb;
--cs-radius: 12px;
--cs-width: 280px;
}/* Colorshot's styles live in @layer colorshot, so your own CSS
(including Tailwind utilities) wins without !important */
.inspector [data-colorshot] {
--cs-width: 100%;
--cs-area-height: 160px;
}| Variable | What it sets |
|---|---|
| --cs-accent | Focus rings and selected states |
| --cs-bg / --cs-fg | Panel background and text |
| --cs-radius | Panel corners |
| --cs-width | Panel width |
| --cs-area-height | Height of the color area |
| --cs-swatch-columns | Swatches per row |
| --cs-shadow | Panel shadow |
Localization
Every visible and screen reader string is a label you can replace.
// Every visible and screen reader string is a label. {placeholders} are filled in for you.
<ColorPicker
value={value}
onChange={setValue}
labels={{
picker: "Farbwähler",
solid: "Einfarbig",
linear: "Linear",
radial: "Radial",
conic: "Konisch",
hue: "Farbton",
alpha: "Deckkraft",
stopName: "Farbstopp {index} von {count}",
}}
/>Gradient handles on your canvas
Show start, end, stop and center handles on the selected layer, like Figma. Two framework-free helpers do the math.
import { gradientHandles, moveGradientHandle } from "@orshot/colorshot";
// Handles for the selected layer, in its own pixel space
const result = gradientHandles(layer.fill, layer.width, layer.height); // null when not a gradient
result?.handles.forEach((h) => drawHandle(h.x, h.y, h.kind, h.color)); // start, end, stops, center
if (result?.line) drawGuide(result.line);
// On pointer move while dragging a handle
layer.fill = moveGradientHandle(layer.fill, handle.id, x, y, layer.width, layer.height, { snap: event.shiftKey });Keyboard and gestures
Built in, no props needed. Shortcuts work while the picker has focus.
| Input | What it does |
|---|---|
| Two-finger swipe | Move the area, sliders, stops and angle on a trackpad |
| Arrow keys | Nudge the focused thumb, stop or field (Shift for 10x) |
| 1 to 4 | Switch Solid, Linear, Radial and Conic (picker focused) |
| I | Open the eyedropper |
| ⌘C / ⌘V | Copy the value, or paste any CSS color or gradient |
| ⌘Z / ⇧⌘Z | Undo and redo, with the history prop |
| Click the bar / Enter | Add a stop |
| Alt + drag a stop | Copy the stop |
| Drag off the bar / Delete | Remove a stop |
| Drag a swatch onto the bar | Add it as a stop |
| Escape | Drop what you typed, or close a ColorField |
Optional features
Off unless you turn them on.
| Prop | What it does |
|---|---|
| space="oklch" | A perceptual OKLCH area and hue slider, drawn in Display P3 where supported |
| history | Undo and redo committed changes with Cmd/Ctrl+Z inside the picker |
| contrastWith="#fff" | WCAG contrast badge; gradients use their weakest stop |
| compare | Before and after swatch; click before to restore |
| tabIcons | Icons next to the Solid, Linear, Radial and Conic labels |
| swatchLayout="stack" | List every swatch group instead of one tab per group |
| size="sm" | Compact: 240px wide, 24px controls |
| variant="inset" | Mode tabs first and a framed area, instead of the edge-to-edge area |
| eyeDropper={fn} | Your own screen picker for browsers without the native EyeDropper |
| shortcuts={false} | Turn off 1 to 4 and I |
| storageKey={null} | Keep recent colors and the chosen format in memory only |
Core helpers
Framework-free functions from @orshot/colorshot. Safe in server code, and none of them throw on bad input.
| Function | What it does |
|---|---|
| createPicker(options) | The framework-free store the React and Vue pickers use. |
| parseColor(css) | Any CSS color to { color, format }, or null. Never throws. |
| formatColor(color, format, style?) | Write a color as hex, rgb, hsl, oklch, p3 and more. |
| isGradient / parseGradient / serializeGradient | Read and write CSS gradients, keeping what you did not edit. |
| gradientHandles / moveGradientHandle | Start, end, stop and center handles for your own canvas. |
| contrastRatio(fg, bg) / contrastLevel(ratio) | WCAG contrast and its AA or AAA level. |
| isSafeCssValue / safeCssValue | Only plain colors and gradients, before a value reaches a style. |
ColorPicker props
The ones you will reach for. The README on GitHub lists every prop.
| Prop | Type | What it does |
|---|---|---|
| value | string | Any CSS color or gradient. Unknown text is shown as unsupported and left as it is. |
| onChange | (value) => void | Fires while dragging or typing, at most once per frame. |
| onChangeComplete | (value) => void | Fires once when a change ends. Save and record undo here. |
| modes | PickerMode[] | Any of solid, linear, radial, conic. Default: all four. |
| outputFormat | string | (color) => string | How picked colors are written. Default keeps the format of the value. |
| alpha | boolean | Show the opacity slider and field. Default true. |
| formats | DisplayFormat[] | Formats in the input menu, for example hex, rgb, hsl, hsb, cmyk. |
| swatches | SwatchGroupConfig[] | Your swatch groups: brand, saved, recent, with add and remove. |
| swatchSearch | boolean | Search every group by name, value or hex. |
| gradientPresets | boolean | string[] | Gradient swatches in gradient modes. |
| theme | light | dark | Force a theme. Default follows the OS. |
| size | sm | Compact: 240px wide, 24px controls. |
| storageKey | string | null | Where recent colors and the chosen format are remembered. null keeps them in memory. |
TypeScript
Types ship with the package, for every entry.
import type { ColorPickerProps, SwatchGroupConfig, Swatch } from "@orshot/colorshot/react";
import type { OutputFormat, PickerMode, Color } from "@orshot/colorshot";Questions
Is Colorshot free to use?
Does it work with Next.js?
Will it break on colors my app already stores?
Does it work with shadcn/ui?
Is there a Vue color picker too?
How big is it?
Who maintains it?
Colorshot powers Orshot Studio, with 10k+ users
Orshot is an API that generates images, PDFs and videos from your templates.