Open source by Orshot

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.

Docs

MIT licensed · v0.1.0 · React 18+ and Vue 3

linear-gradient(135deg, #3E5CEB 0%, #A855F7 52%, #F97316 100%)
4 gradient typesLinear, radial, conic and repeating, with draggable stops
10 color formatsHEX, RGB, HSL, HSB, OKLCH, OKLAB, LCH, LAB, Display P3, CMYK
WCAG 2.2 AAChecked with axe. Keyboard, screen readers, high contrast, RTL
1 update per frameDragging re-renders only the parts that change
44 KB gzippedReact app with styles, zero dependencies
450 testsIn Chromium, Firefox and WebKit, plus 31,731 real colors
Try it

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.

LinearRadialConicRepeating

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.

Swipe to moveAlt-drag to copyDrag off to removeClick to add

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)
Drag the handles

Paste any CSS color

Hex, rgb, hsl, oklch, named or display-p3. Press Cmd+V in the picker.

reads as #663399

Contrast as you pick

A WCAG badge against your background. For gradients it checks the weakest stop.

Summer SaleUp to 40% off, this weekend only
Contrast5.34:1AA

Brand and saved colors

Your own swatch groups, with add, remove, rename, reorder and search.

Brand
  • 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.
In production

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.

Loading the editor…

Select a shape, then click its fill color to open Colorshot.

Documentation

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/colorshot
pnpm add @orshot/colorshot
yarn add @orshot/colorshot
bun add @orshot/colorshot
ImportWhat it is
@orshot/colorshot/reactReact components and hooks
@orshot/colorshot/vueVue components and composables
@orshot/colorshotColor math, CSS gradient parsing and the picker store. Safe in server code
@orshot/colorshot/styles.cssThe 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.

Setup prompt
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/colorshot

Or 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/colorshot

ColorPicker

The ready-made panel: area, sliders, every gradient mode, inputs and swatch groups.

Gradients
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 />;
}
PropTypeWhat it does
label / placeholderstringTrigger label, and the text shown when the value is empty.
placementbottom-start | bottom-end | top-start | top-end | right-start | left-startPreferred side. It flips and shifts to stay on screen.
open / defaultOpen / onOpenChangeboolean, (open) => voidControl the popover, or leave it uncontrolled.
portalboolean | HTMLElementRender the popover elsewhere in the DOM.
renderTrigger({ value, open }) => ReactNodeYour own trigger button.
pickerClassName / pickerStylestring, CSSPropertiesStyle the picker inside the popover.
...ColorPicker propsEvery 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.

A compact solid picker
#22C55E
import { 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>
  );
}
A brand palette, swatches only
Brand
#3E5CEB
import { 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>
  );
}
PartWhat it is
Picker.RootThe panel and the state every other part reads. Takes the ColorPicker value props.
Picker.AreaThe 2D saturation and brightness area.
Picker.Hue / Picker.AlphaThe hue and opacity sliders.
Picker.ModeTabsSolid, Linear, Radial and Conic tabs.
Picker.GradientEditorBar plus controls, shown only in gradient modes.
Picker.GradientBarThe stop bar on its own.
Picker.GradientControls / Picker.AngleDial / Picker.CenterPadAngle, shape, size and center controls.
Picker.InputsFormat menu and channel fields (hex, rgb, hsl, ...).
Picker.EyeDropperScreen color picking where the browser supports it.
Picker.SwatchesSwatch groups with tabs, search, add and remove.
Picker.CurrentSwatch / Picker.PreviewThe current value, with an optional before and after.
Picker.ContrastWCAG contrast badge against a background.
Picker.NoticeShown 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.

Gradients
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.

Contrast4.76:1AA

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.

#F97316
import { 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;
}
VariableWhat it sets
--cs-accentFocus rings and selected states
--cs-bg / --cs-fgPanel background and text
--cs-radiusPanel corners
--cs-widthPanel width
--cs-area-heightHeight of the color area
--cs-swatch-columnsSwatches per row
--cs-shadowPanel 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.

InputWhat it does
Two-finger swipeMove the area, sliders, stops and angle on a trackpad
Arrow keysNudge the focused thumb, stop or field (Shift for 10x)
1 to 4Switch Solid, Linear, Radial and Conic (picker focused)
IOpen the eyedropper
⌘C / ⌘VCopy the value, or paste any CSS color or gradient
⌘Z / ⇧⌘ZUndo and redo, with the history prop
Click the bar / EnterAdd a stop
Alt + drag a stopCopy the stop
Drag off the bar / DeleteRemove a stop
Drag a swatch onto the barAdd it as a stop
EscapeDrop what you typed, or close a ColorField

Optional features

Off unless you turn them on.

PropWhat it does
space="oklch"A perceptual OKLCH area and hue slider, drawn in Display P3 where supported
historyUndo and redo committed changes with Cmd/Ctrl+Z inside the picker
contrastWith="#fff"WCAG contrast badge; gradients use their weakest stop
compareBefore and after swatch; click before to restore
tabIconsIcons 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.

FunctionWhat 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 / serializeGradientRead and write CSS gradients, keeping what you did not edit.
gradientHandles / moveGradientHandleStart, end, stop and center handles for your own canvas.
contrastRatio(fg, bg) / contrastLevel(ratio)WCAG contrast and its AA or AAA level.
isSafeCssValue / safeCssValueOnly 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.

PropTypeWhat it does
valuestringAny CSS color or gradient. Unknown text is shown as unsupported and left as it is.
onChange(value) => voidFires while dragging or typing, at most once per frame.
onChangeComplete(value) => voidFires once when a change ends. Save and record undo here.
modesPickerMode[]Any of solid, linear, radial, conic. Default: all four.
outputFormatstring | (color) => stringHow picked colors are written. Default keeps the format of the value.
alphabooleanShow the opacity slider and field. Default true.
formatsDisplayFormat[]Formats in the input menu, for example hex, rgb, hsl, hsb, cmyk.
swatchesSwatchGroupConfig[]Your swatch groups: brand, saved, recent, with add and remove.
swatchSearchbooleanSearch every group by name, value or hex.
gradientPresetsboolean | string[]Gradient swatches in gradient modes.
themelight | darkForce a theme. Default follows the OS.
sizesmCompact: 240px wide, 24px controls.
storageKeystring | nullWhere 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";
FAQ

Questions

Is Colorshot free to use?

Yes. It is MIT licensed, so you can use it in personal and commercial projects.

Does it work with Next.js?

Yes. Render the picker from a client component ("use client"). The framework-free entry, @orshot/colorshot, is safe to import in server code.

Will it break on colors my app already stores?

No. Before release we ran every color in Orshot's production templates through it, 31,731 distinct values. Anything it cannot read is shown as unsupported and left as it is.

Does it work with shadcn/ui?

Yes. Install it next to your shadcn/ui components and map its CSS variables to your theme tokens (--primary, --popover, --border, --radius), and it follows your theme and dark mode.

Is there a Vue color picker too?

Yes. The same picker ships for Vue 3 from @orshot/colorshot/vue, with v-model support.

How big is it?

About 44 KB gzipped for a React app, styles included, with zero dependencies. A React app never loads the Vue code, and the reverse.

Who maintains it?

Orshot, the API for automated image, PDF and video generation. Colorshot is the picker inside the Orshot template editor.
Made by Orshot

Colorshot powers Orshot Studio, with 10k+ users

Orshot is an API that generates images, PDFs and videos from your templates.