npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@markoradak/color-picker

v0.1.4

Published

A compound-component React color picker and gradient editor

Readme

@markoradak/color-picker

A compound-component React color picker and gradient editor. Built with Radix primitives, fully accessible, and themeable via CSS custom properties or Tailwind CSS.

Installation

pnpm add @markoradak/color-picker
# or
npm install @markoradak/color-picker
# or
yarn add @markoradak/color-picker

Peer dependencies: react >= 18.0.0 and react-dom >= 18.0.0

Quick Start

Pre-composed Popover (easiest)

A ready-to-use popover picker with all controls included:

import { useState } from "react";
import { ColorPickerPopover } from "@markoradak/color-picker/presets";
import "@markoradak/color-picker/styles";

function App() {
  const [color, setColor] = useState("#3b82f6");

  return (
    <ColorPickerPopover
      value={color}
      onValueChange={setColor}
      swatches={["#ef4444", "#22c55e", "#3b82f6"]}
    />
  );
}

Pre-composed Inline

An always-visible picker (no popover):

import { useState } from "react";
import { ColorPickerInline } from "@markoradak/color-picker/presets";
import "@markoradak/color-picker/styles";

function App() {
  const [color, setColor] = useState("#3b82f6");

  return (
    <ColorPickerInline value={color} onValueChange={setColor} />
  );
}

Compound Components (full control)

Build your own layout by composing individual primitives. Both dot notation (ColorPicker.Area) and named imports (ColorPickerArea) work:

import { useState } from "react";
import { ColorPicker } from "@markoradak/color-picker";
import "@markoradak/color-picker/styles";

function MyColorPicker() {
  const [color, setColor] = useState("#3b82f6");

  return (
    <ColorPicker value={color} onValueChange={setColor}>
      <ColorPicker.Trigger />
      <ColorPicker.Content>
        <ColorPicker.Area>
          <ColorPicker.AreaGradient />
          <ColorPicker.AreaThumb />
        </ColorPicker.Area>
        <ColorPicker.HueSlider>
          <ColorPicker.HueSliderTrack />
          <ColorPicker.HueSliderThumb />
        </ColorPicker.HueSlider>
        <ColorPicker.AlphaSlider>
          <ColorPicker.AlphaSliderTrack />
          <ColorPicker.AlphaSliderThumb />
        </ColorPicker.AlphaSlider>
        <ColorPicker.Input />
        <ColorPicker.EyeDropper />
        <ColorPicker.Swatches
          values={["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6"]}
        />
      </ColorPicker.Content>
    </ColorPicker>
  );
}

Or using named imports:

import {
  ColorPicker,
  ColorPickerTrigger,
  ColorPickerContent,
  ColorPickerArea,
  ColorPickerAreaGradient,
  ColorPickerAreaThumb,
  ColorPickerHueSlider,
  ColorPickerHueSliderTrack,
  ColorPickerHueSliderThumb,
  ColorPickerInput,
  ColorPickerEyeDropper,
  ColorPickerSwatches,
  ColorPickerSwatch,
} from "@markoradak/color-picker";

Gradient Picker

Pass a GradientValue object instead of a string to enable gradient editing:

import { useState } from "react";
import { ColorPicker, createDefaultGradient } from "@markoradak/color-picker";
import type { ColorPickerValue } from "@markoradak/color-picker";
import "@markoradak/color-picker/styles";

function GradientPicker() {
  const [value, setValue] = useState<ColorPickerValue>(
    createDefaultGradient("linear")
  );

  return (
    <ColorPicker value={value} onValueChange={setValue}>
      <ColorPicker.Trigger />
      <ColorPicker.Content>
        <ColorPicker.ModeSelector>
          {(["solid", "linear", "radial", "conic", "mesh"] as const).map(
            (mode) => (
              <ColorPicker.ModeSelectorItem key={mode} value={mode} />
            )
          )}
        </ColorPicker.ModeSelector>
        <ColorPicker.GradientEditor />
        <ColorPicker.Area>
          <ColorPicker.AreaGradient />
          <ColorPicker.AreaThumb />
        </ColorPicker.Area>
        <ColorPicker.HueSlider>
          <ColorPicker.HueSliderTrack />
          <ColorPicker.HueSliderThumb />
        </ColorPicker.HueSlider>
        <ColorPicker.Input />
      </ColorPicker.Content>
    </ColorPicker>
  );
}

Supported gradient types: linear, radial, conic, and mesh.

Color Tokens

The picker supports named color tokens. Tokens can be provided manually or auto-detected from CSS custom properties:

// Manual tokens
<ColorPicker
  value="brand-primary"
  onValueChange={setValue}
  tokens={{
    "brand-primary": "#3b82f6",
    "brand-secondary": "#8b5cf6",
    danger: "#ef4444",
  }}
/>

// Auto-detect CSS custom properties from :root / html
<ColorPicker value={color} onValueChange={setColor} autoTokens />

// Auto-detect with prefix filter (strips prefix from display names)
<ColorPicker value={color} onValueChange={setColor} autoTokens={{ prefix: "--brand-" }} />

// Disable auto-detection (enabled by default)
<ColorPicker value={color} onValueChange={setColor} autoTokens={false} />

When tokens are available, the input field shows a token badge. Clicking it opens a searchable dropdown listing all tokens.

Input Trigger

Use ColorPickerInputTrigger (or triggerMode="input" on presets) for an input-style trigger that shows the color value inline:

<ColorPicker value={color} onValueChange={setColor}>
  <ColorPickerInputTrigger />
  <ColorPicker.Content>
    {/* ... controls ... */}
  </ColorPicker.Content>
</ColorPicker>

Contrast Checking (WCAG)

Both presets render a contrast-info row and a threshold line across the color area when a contrastColor is passed:

<ColorPickerPopover
  value={color}
  onValueChange={setColor}
  contrastColor="#ffffff"
  onContrastColorChange={setBg}
/>

For the composable API, place ColorPickerContrastInfo where you want the ratio readout and ColorPickerContrastLine inside ColorPickerArea between the gradient and the thumb:

<ColorPicker value={color} onValueChange={setColor}>
  <ColorPickerContrastInfo contrastColor="#ffffff" onContrastColorChange={setBg} />
  <ColorPickerArea>
    <ColorPickerAreaGradient />
    <ColorPickerContrastLine contrastColor="#ffffff" />
    <ColorPickerAreaThumb />
  </ColorPickerArea>
  {/* ... */}
</ColorPicker>

ColorPickerContrastInfo shows the current ratio (e.g. 5.23 : 1) and a WCAG level badge (AAA, AA, AA18, or Insufficient). ColorPickerContrastLine renders an SVG curve along the 4.5:1 threshold inside the saturation/brightness area, with a dot pattern in the failing region. Pass threshold={3} to target AA Large.

ColorPickerProvider (advanced)

ColorPickerProvider is a context-only wrapper (no Radix Popover.Root) that manages a single solid color. Use it when you need to embed color-picker controls inside another popover — for example, a per-stop color editor inside the gradient editor's own popover. For standard usage, prefer ColorPicker.

Styling

The library ships unstyled primitives. There are three ways to style them:

1. CSS Custom Properties Theme (recommended for most apps)

Import the included stylesheet for a complete, themeable look:

import "@markoradak/color-picker/styles";

Override CSS custom properties to match your design system:

.my-theme {
  --cp-bg: #1a1a2e;
  --cp-border: #2a2a4a;
  --cp-border-focus: #60a5fa;
  --cp-text: #f5f5f5;
  --cp-radius: 12px;
  --cp-width: 280px;
}

2. Tailwind CSS Classes

Pass Tailwind classes via className and classNames props:

<ColorPickerArea className="relative h-44 w-full cursor-crosshair rounded-lg">
  <ColorPickerAreaGradient className="rounded-lg" />
  <ColorPickerAreaThumb className="h-4 w-4 rounded-full border-2 border-white shadow" />
</ColorPickerArea>

Components with multiple inner elements expose a classNames prop:

<ColorPickerInput
  className="flex items-center gap-1"
  classNames={{
    formatToggle: "rounded-md border px-2 h-8 text-xs",
    field: "w-full rounded-md border px-2 h-8 text-sm",
  }}
/>

3. Unstyled (fully custom)

Use the components without importing any styles and target them with CSS selectors:

[data-cp-part="area"] { /* ... */ }
[data-cp-part="hue-slider"] [data-cp-el="thumb"] { /* ... */ }

All components render data-cp-part and data-cp-el attributes for CSS targeting.

API Reference

Components

| Component | Dot Notation | Description | |---|---|---| | ColorPicker | - | Root provider. Wraps children in context and Radix Popover. | | ColorPickerTrigger | ColorPicker.Trigger | Button that opens the popover. Displays current color swatch. | | ColorPickerInputTrigger | - | Input-style trigger with thumbnail, text input, format toggle, and eye dropper. | | ColorPickerContent | ColorPicker.Content | Popover content container with positioning. | | ColorPickerArea | ColorPicker.Area | 2D saturation/brightness picker container. | | ColorPickerAreaGradient | ColorPicker.AreaGradient | Renders the white-to-black gradient overlays inside the area. | | ColorPickerAreaThumb | ColorPicker.AreaThumb | Draggable thumb positioned by saturation and brightness. | | ColorPickerHueSlider | ColorPicker.HueSlider | Hue slider container (0-360). | | ColorPickerHueSliderTrack | ColorPicker.HueSliderTrack | Rainbow gradient track. | | ColorPickerHueSliderThumb | ColorPicker.HueSliderThumb | Draggable hue thumb. | | ColorPickerAlphaSlider | ColorPicker.AlphaSlider | Alpha slider container (0-1). | | ColorPickerAlphaSliderTrack | ColorPicker.AlphaSliderTrack | Checkerboard + alpha gradient track. | | ColorPickerAlphaSliderThumb | ColorPicker.AlphaSliderThumb | Draggable alpha thumb. | | ColorPickerInput | ColorPicker.Input | Text input showing color in current format. Validates on blur/Enter. | | ColorPickerFormatToggle | ColorPicker.FormatToggle | Cycles between HEX, RGB, and HSL display formats. | | ColorPickerEyeDropper | ColorPicker.EyeDropper | Browser EyeDropper API button. Renders nothing in unsupported browsers. | | ColorPickerSwatches | ColorPicker.Swatches | Grid container for preset color swatches. | | ColorPickerSwatch | ColorPicker.Swatch | Individual color swatch button. | | ColorPickerModeSelector | ColorPicker.ModeSelector | Segmented control for switching between solid and gradient modes. | | ColorPickerModeSelectorItem | ColorPicker.ModeSelectorItem | Individual mode button (solid, linear, radial, conic, mesh). | | ColorPickerGradientEditor | ColorPicker.GradientEditor | Self-contained gradient editing UI with preview and stop manipulation. | | ColorPickerGradientSwatches | ColorPicker.GradientSwatches | Grid container for preset gradient swatches. | | ColorPickerGradientSwatch | ColorPicker.GradientSwatch | Individual gradient swatch button. | | ColorPickerContrastInfo | ColorPicker.ContrastInfo | WCAG contrast-ratio readout with level badge. Optionally opens a mini picker to change the reference color. | | ColorPickerContrastLine | ColorPicker.ContrastLine | SVG overlay for ColorPickerArea that draws the WCAG threshold curve and a dot pattern in the failing region. | | GradientPreview | - | Lower-level gradient preview with stop dots and drag handles. | | GradientStops | - | Lower-level horizontal stop bar with draggable markers. | | TokenList | - | Lower-level searchable token list dropdown. | | ColorPickerProvider | ColorPicker.Provider | Context-only provider for solid colors (no Popover.Root). Used internally for per-stop color editing in gradient mode. |

Presets

Pre-composed components that bundle all controls together:

| Component | Description | |---|---| | ColorPickerPopover | Trigger + popover with all controls. | | ColorPickerInline | Always-visible picker with all controls. | | ColorPickerControls | Shared inner controls used by both presets. Can be used standalone inside a ColorPicker or ColorPickerProvider. |

Import from @markoradak/color-picker/presets or from the main entry.

ColorPicker Props

| Prop | Type | Default | Description | |---|---|---|---| | value | ColorPickerValue | - | Controlled value (string or GradientValue). | | onValueChange | (value: ColorPickerValue) => void | - | Called when value changes. | | defaultValue | ColorPickerValue | "#000000" | Initial value for uncontrolled mode. | | disabled | boolean | false | Disables all interactions. | | defaultOpen | boolean | false | Whether the popover starts open (uncontrolled). | | tokens | ColorTokens | - | Map of semantic token names to color values. | | autoTokens | AutoTokensConfig | true | Auto-detect CSS custom property color tokens. true = detect all, false = disable, { prefix: "--brand-" } = filter by prefix. | | children | ReactNode | - | Compound sub-components. |

ColorPickerPopover Props

Includes all props from ColorPickerPresetProps plus popover-specific options:

| Prop | Type | Default | Description | |---|---|---|---| | value | ColorPickerValue | - | Controlled value. | | onValueChange | (value: ColorPickerValue) => void | - | Value change callback. | | defaultValue | ColorPickerValue | "#000000" | Initial uncontrolled value. | | disabled | boolean | false | Disables all interactions. | | enableAlpha | boolean | true | Show the alpha/opacity slider. | | enableGradient | boolean | true | Show gradient editor controls. | | enableModeSelector | boolean | true when enableGradient is true | Show the solid/gradient mode selector. | | enableEyeDropper | boolean | true | Show EyeDropper button. | | enableFormatToggle | boolean | true | Show format cycle button. | | enableTokenSearch | boolean | true | Show search input in token dropdown. | | enableSwatches | boolean | true | Show preset swatches. | | swatches | string[] | Built-in palette | Preset swatch colors (solid mode). | | swatchColumns | number | 8 | Grid columns for swatches. | | gradientSwatches | GradientValue[] | - | Preset gradient swatches (gradient mode). | | contrastColor | string | - | Reference color for the WCAG contrast row and threshold line (solid mode). When provided, contrast UI is rendered; omit to hide. | | onContrastColorChange | (color: string) => void | - | Called when the user changes the reference color via the contrast-indicator popover. | | tokens | ColorTokens | - | Map of semantic token names to color values. | | autoTokens | AutoTokensConfig | true | Auto-detect CSS custom property tokens. | | side | "top" \| "right" \| "bottom" \| "left" | "bottom" | Popover placement. | | align | "start" \| "center" \| "end" | "center" | Popover alignment. | | sideOffset | number | 4 | Offset from trigger (px). | | trigger | ReactNode | - | Custom trigger element (wraps in ColorPickerTrigger asChild). | | triggerMode | "thumbnail" \| "input" | "thumbnail" | Trigger style. "input" renders ColorPickerInputTrigger. Ignored when trigger is provided. | | className | string | - | Additional CSS class on the wrapper. |

ColorPickerInline Props

Same as ColorPickerPopover except without side, align, sideOffset, trigger, and triggerMode.

Utilities

| Function | Description | |---|---| | toCSS(value) | Convert a ColorPickerValue to a CSS string. | | fromCSS(css) | Parse a CSS gradient string into a ColorPickerValue. Supports linear-gradient, radial-gradient, conic-gradient, and repeating-* variants. Returns the raw string for unparseable input. | | isGradient(value) | Type guard: is the value a gradient object? | | isSolidColor(value) | Type guard: is the value a solid color string? | | formatColor(input, format) | Convert a color string to hex/rgb/hsl. | | detectFormat(input) | Detect the format of a color string. | | parseColor(input) | Parse any color string into a colord instance. | | isValidColor(input) | Check if a string is a valid color. | | toHSVA(input) | Convert a color string to HSVA. Returns white for invalid input. | | fromHSVA(hsva) | Convert HSVA values back to a hex string. | | getContrastColor(bg) | Returns "black" or "white" for best contrast against the given background. | | contrastRatio(a, b) | Compute the WCAG 2.1 contrast ratio between two colors (1–21). | | getWcagLevel(ratio) | Classify a ratio as "AAA", "AA", "AA18" (large text), or "Fail". Exported as type WcagLevel. | | getEffectiveBackgroundColor(element) | Walk up the DOM from element and return the first non-transparent background color (falls back to white). | | colorLuminance(color) | Relative luminance of any CSS color string (0–1). | | hsvLuminance(h, s, v, a) | Relative luminance for HSVA values — useful for computing contrast boundaries across the picker area. | | contrastFromLuminances(l1, l2) | Contrast ratio from two pre-computed luminances. | | resolveToken(value, tokens) | Resolve a token name to its color value via the tokens map. Returns the value unchanged if not found. | | findMatchingToken(hex, tokens) | Find the token name whose resolved color matches the given hex. | | getCSSColorTokens(prefix?) | Scan the DOM for CSS custom properties that resolve to valid colors. | | createDefaultGradient(type) | Create a default gradient of the given type. | | createDefaultGradientFromColor(type, color) | Create a gradient from an existing color. | | createGradientStop(color, position) | Create a gradient stop object with a generated ID. | | createMeshGradientStop(color, position, x, y) | Create a mesh gradient stop with 2D coordinates. | | interpolateColorAt(stops, position) | Interpolate a color at a position along the gradient. | | addStop(gradient, color, position) | Add a stop to a gradient (returns new gradient). | | addStopWithCoordinates(gradient, color, position, x, y) | Mesh-only: add a stop with explicit 2D coordinates. | | removeStop(gradient, stopId) | Remove a stop by ID (returns new gradient). | | updateStop(gradient, stopId, updates) | Update a stop's color or position (returns new gradient). | | moveStop(gradient, stopId, direction) | Reorder a stop (mesh only): "front", "forward", "backward", "back". | | sortStops(stops) | Sort stops by position (returns new array). | | clamp(n, min, max) | Constrain a number to a range. | | getRelativePosition(event, element) | Pointer position inside an element normalized to 0–1 on each axis. | | angleFromPosition(cx, cy, x, y) | Angle in degrees from a center point to an (x, y) coordinate. |

Hooks

| Hook | Description | |---|---| | useColorPicker(options) | Core state management hook. Manages HSVA state, format cycling, controlled/uncontrolled value, and token resolution. | | useGradient(options) | Gradient state management. Manages active stop, stop CRUD, and gradient replacement. | | usePointerDrag(options) | Generic pointer drag hook used by Area and Slider components. | | useAutoTokens(config, manualTokens) | Merges auto-detected CSS custom property tokens with manually provided tokens. | | useTokenDropdown(options) | State machine for the token dropdown: open/close, search, keyboard navigation, click-outside dismissal. |

Types

type SolidColor = string;
type ColorFormat = "hex" | "rgb" | "hsl" | "oklch";
type GradientType = "linear" | "radial" | "conic" | "mesh";
type ColorPickerMode = "solid" | GradientType;
type ColorPickerValue = SolidColor | GradientValue;
type ColorTokens = Record<string, string>;
type AutoTokensConfig = { prefix?: string } | boolean;

// GradientValue is a discriminated union -- switch on `value.type`
type GradientValue =
  | LinearGradientValue
  | RadialGradientValue
  | ConicGradientValue
  | MeshGradientValue;

interface LinearGradientValue {
  type: "linear";
  stops: GradientStop[];
  angle: number; // degrees
}

interface RadialGradientValue {
  type: "radial";
  stops: GradientStop[];
  centerX: number; // 0-100
  centerY: number; // 0-100
}

interface ConicGradientValue {
  type: "conic";
  stops: GradientStop[];
  angle: number; // degrees
  centerX: number; // 0-100
  centerY: number; // 0-100
}

interface MeshGradientValue {
  type: "mesh";
  stops: MeshGradientStop[];
  baseColor?: string; // solid color behind radial blobs
}

interface GradientStop {
  id: string;
  color: string;
  position: number; // 0-100
}

interface MeshGradientStop {
  id: string;
  color: string;
  position: number; // 0-100
  x: number; // 0-100
  y: number; // 0-100
}

interface HSVA {
  h: number; // 0-360
  s: number; // 0-100
  v: number; // 0-100
  a: number; // 0-1
}

CSS Theming

Import the default stylesheet and override CSS custom properties:

@import "@markoradak/color-picker/styles";

.my-theme {
  --cp-bg: #1a1a2e;
  --cp-border: #2a2a4a;
  --cp-border-focus: #60a5fa;
  --cp-text: #f5f5f5;
  --cp-radius: 12px;
  --cp-width: 280px;
}

Dark Mode

Dark mode is applied automatically via prefers-color-scheme: dark. You can also use a .dark class on any ancestor element for manual toggling:

<div class="dark">
  <!-- All color pickers inside will use dark theme -->
</div>

Available Custom Properties

| Property | Default (Light) | Description | |---|---|---| | --cp-bg | #ffffff | Panel background | | --cp-bg-secondary | #fafafa | Secondary background (mode selector, token badge) | | --cp-border | #e5e5e5 | Border color | | --cp-border-focus | #3b82f6 | Focus ring / active indicator color | | --cp-radius | 0.75rem | Border radius (panel) | | --cp-radius-sm | 0.5rem | Border radius (controls) | | --cp-radius-full | 9999px | Border radius (thumbs, swatches) | | --cp-text | #171717 | Primary text color | | --cp-text-muted | #737373 | Muted text color | | --cp-input-bg | #ffffff | Input background | | --cp-input-border | #d4d4d4 | Input border | | --cp-shadow | - | Panel box-shadow | | --cp-shadow-sm | - | Small box-shadow (mode selector active) | | --cp-ring | 0 0 0 2px var(--cp-border-focus) | Focus ring box-shadow | | --cp-ring-offset | 2px | Focus ring outline offset | | --cp-hover-bg | #f5f5f5 | Hover background | | --cp-active-border | #1f2937 | Active swatch border | | --cp-width | 18rem | Panel width | | --cp-area-height | 11rem | Color area height | | --cp-slider-height | 0.75rem | Slider track height | | --cp-thumb-size | 1rem | Thumb size | | --cp-swatch-size | 1.75rem | Swatch button size | | --cp-gap | 0.75rem | Spacing between controls | | --cp-padding | 0.75rem | Panel padding | | --cp-checkerboard-color | #e5e5e5 | Checkerboard pattern color | | --cp-z-index-dropdown | 50 | z-index for dropdowns | | --cp-z-index-portal | 99999 | z-index for portals | | --cp-font-family | monospace | Font for color value inputs | | --cp-transition-duration | 0.15s | Transition duration (respects prefers-reduced-motion) |

Accessibility

  • Full keyboard navigation (arrow keys, Enter, Escape, Tab)
  • ARIA roles and labels on all interactive elements
  • prefers-reduced-motion: reduce disables all animations
  • Focus-visible rings on all focusable elements
  • Screen reader announcements for color changes

Browser Support

  • All modern browsers (Chrome, Firefox, Safari, Edge)
  • The EyeDropper button uses the EyeDropper API (Chromium-based browsers). It automatically hides in unsupported browsers.

Development

This package lives in a pnpm + Turborepo monorepo alongside a Next.js demo site. Requires Node 18+ and pnpm 10+.

packages/
  react/          @markoradak/color-picker — this package
apps/
  web/            Next.js demo + playground site
pnpm install
pnpm dev          # tsup --watch + next dev, concurrently
pnpm build        # build the package, then the demo site
pnpm test         # 261 tests in packages/react
pnpm typecheck    # typecheck all workspaces

License

MIT