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

react-piqua-color

v1.0.4

Published

A portable, fully controlled React color picker: SV square, hue bar, and hex/rgb/hsl/cmyk sliders. Self-contained styling via CSS custom properties, no CSS framework required.

Downloads

868

Readme

react-piqua-color

▶ Try the live demo

A portable, fully controlled React color picker. It renders an SV (saturation/value) square, a hue bar, segmented mode tabs, and labelled channel sliders for hex / rgb / hsl / cmyk. All the color math and the sticky-hue behaviour that keeps hue stable through degenerate colors (black, white, gray) are baked in.

Styling is fully self-contained via a single stylesheet driven by CSS custom properties, no Tailwind, no CSS framework required. Drop it into any React app.

Features

  • SV (saturation/value) square, hue bar, and an optional opacity bar
  • hex / rgb / hsl / cmyk modes, with a sticky hue that survives black, white, and gray
  • Opt-in alpha: 8-digit hex plus an opacity slider
  • "Default colors" and "recents" rows, placeable above or below the picker; the swatch matching the current color is shown selected
  • Eyedropper (where the browser supports it) and copy-to-clipboard
  • Fully keyboard operable and screen-reader labelled
  • CSS custom-property theming, a ready-made dark preset, and per-part class hooks
  • Fully controlled, zero runtime dependencies, TypeScript types included

Install

npm install react-piqua-color

react and react-dom (>=18) are peer dependencies.

Usage

import { useState } from 'react'
import { ColorPicker } from 'react-piqua-color'
import 'react-piqua-color/style.css'

const DEFAULT_SWATCHES = ['#f97316', '#ef4444', '#22c55e', '#3b82f6', '#a855f7', '#000000', '#ffffff']

function Example() {
   const [color, setColor] = useState('#f97316')

   // The consumer owns recents storage. The picker never stores, caps, or dedupes.
   const [recents, setRecents] = useState<string[]>([])

   return (
      <ColorPicker
         value={color}
         onChange={setColor}                 // live: fires during slider drag / hex typing
         swatches={DEFAULT_SWATCHES}
         recentColors={recents}
         onColorCommitted={committedColor => {
            // discrete commit: swatch click, hex completed, slider pointer-up.
            setRecents(previous => [committedColor, ...previous.filter(existing => existing !== committedColor)].slice(0, 12))
         }}
      />
   )
}

The component is fully controlled: it renders value, calls onChange on every live edit, and never holds the "official" color itself. The swatches and recentColors arrays are display-only: the picker renders a clickable square per entry and calls onChange then onColorCommitted when one is clicked. It stores, caps, dedupes, and persists nothing; that is entirely the consumer's job. Whichever swatch or recent equals the current value is shown selected (matched case-insensitively and shorthand-aware, respecting alpha when alpha is on).

Props

| Prop | Type | Required | Description | | ------------------ | --------------------------- | -------- | ------------------------------------------------------------------------------------------- | | value | string | yes | Current color as #rrggbb, or #rrggbbaa when alpha is on. Fully controlled. | | onChange | (hex: string) => void | yes | Live change, fires continuously during slider drag and hex typing. Same width as value. | | alpha | boolean | no | Enable the opacity channel: adds an alpha slider and widens hex to 8 digits. Default false.| | onColorCommitted | (hex, source) => void | no | Discrete commit; source says which interaction (see Commit source). | | swatches | string[] | no | "Default colors" row. Clickable display only; omit to hide the row. | | recentColors | string[] | no | Recents row. Clickable display only; omit to hide the row. | | swatchesLabel | string | no | Label above the swatches row. Default "Default colors". | | recentLabel | string | no | Label above the recents row. Default "Recent". | | swatchesPosition | 'top' \| 'bottom' | no | Place the swatches + recents block above or below the picker body. Default 'bottom'. | | className | string | no | Appended to the root's class, alongside pqc-root. | | style | React.CSSProperties | no | Merged onto the root's inline style, handy for setting --pqc-* tokens inline. | | classNames | ColorPickerClassNames | no | Extra classes for individual parts (see Part-level classes). |

Commit source

onColorCommitted receives the source of the change as its second argument, so you can react differently per interaction, for example closing a popover only when a swatch is clicked:

<ColorPicker
   value={color}
   onChange={setColor}
   onColorCommitted={(hex, source) => {
      addToRecents(hex)
      if (source === 'swatch') closePicker()
   }}
/>

source is one of:

| Value | Interaction | | --- | --- | | 'swatch' | A click in the "Default colors" row | | 'recent' | A click in the recents row | | 'input' | A value typed into the hex field or a channel number box | | 'slider' | A drag or keypress on the SV square, hue / opacity bar, or a channel slider | | 'eyedropper' | A color picked with the screen eyedropper |

The CommitSource union type is exported for your handlers.

Alpha / opacity

Off by default. Pass alpha to add an opacity slider below the hue bar and switch the whole component to 8-digit hex:

const [color, setColor] = useState('#f97316ff')

<ColorPicker alpha value={color} onChange={setColor} />

With alpha on, value and every callback are #rrggbbaa (a 6-digit value is read as fully opaque). The slider reads and announces whole percent (0–100%), while opacity is stored at full 8-bit precision, so an imported #rrggbbaa round-trips losslessly. With alpha off, everything stays #rrggbb exactly as before. Swatches may carry alpha too; the checkerboard shows through wherever a color is translucent.

The hex field accepts 3- and 6-digit input (plus 4- and 8-digit when alpha is on); shorthand like f80 expands to #ff8800 on blur. Output is always full length.

Beside the hex field are a copy button and, where the browser supports the EyeDropper API, an eyedropper to pick a color from anywhere on screen. The eyedropper button is feature-detected and simply absent where unsupported (e.g. Firefox).

Accessibility

Every control is keyboard operable and labelled for assistive tech:

| Control | Keys | | --- | --- | | SV square | Arrow keys move saturation (←→) and brightness (↑↓); Shift for a ×10 step; Home / End set saturation to 0 / 100 | | Hue bar, opacity bar, channel sliders | Arrows adjust by 1; Shift+Arrow / PageUp / PageDown by 10; Home / End jump to the ends | | Mode tabs | Left / Right / Home / End move between hex / rgb / hsl / cmyk |

Sliders expose role="slider" with live aria-valuetext (percentages, degrees), the tabs are a proper role="tablist", and the swatch rows are labelled groups. Focus rings appear for keyboard users only. Continuous edits fire onChange; the discrete onColorCommitted fires once when a control is released or left (handy for a recents list).

Theming

Import the stylesheet once (import 'react-piqua-color/style.css'). Every knob is a CSS custom property with a sensible light-mode default. Override any --pqc-* token on the picker itself (via the className/style props) or on any ancestor. Both inherit correctly.

Dark mode

A ready-made dark theme ships as the opt-in pqc-dark class. Put it on the picker:

<ColorPicker className="pqc-dark" value={color} onChange={setColor} />

or on any ancestor. To follow the OS preference, opt in through your own media query:

@media (prefers-color-scheme: dark) {
   .my-panel .pqc-root { /* re-declare the pqc-dark tokens, or add the class in JS */ }
}

Fine-tune by overriding individual tokens after the class, e.g. style={{ ['--pqc-accent']: '#3b82f6' }}.

Overridable variables

| Variable | Default | Purpose | | ------------------- | ------------------ | ------------------------------------------------ | | --pqc-surface | #ffffff | Raised surface: the active mode tab. | | --pqc-bg | #f1f2f4 | Recessed background: tab strip, hex field. | | --pqc-text | #1a1d21 | Primary text. | | --pqc-muted | #6b7280 | Secondary / muted text. | | --pqc-border | #d4d7dd | Hairline borders. | | --pqc-accent | #f97316 | Accent: focus rings. | | --pqc-thumb-ring | #ffffff | Border color of the circular thumbs. | | --pqc-font-mono | system mono stack | Monospace font used for values and labels. | | --pqc-font-size | 0.75rem | Base text size. | | --pqc-radius | 0.5rem | Corner radius for larger surfaces. | | --pqc-radius-sm | 0.375rem | Corner radius for small surfaces. | | --pqc-gap | 0.625rem | Vertical gap between the picker's sections. | | --pqc-sv-height | 120px | Height of the SV (saturation/value) square. | | --pqc-hue-height | 0.75rem | Thickness of the hue bar. | | --pqc-track-height| 0.5rem | Thickness of the channel slider tracks. | | --pqc-thumb-size | 0.875rem | Diameter of the thumbs (the hue thumb is +2px). | | --pqc-swatch-size | 1.25rem | Size of the swatch / recent squares. | | --pqc-focus-width | 2px | Width of the keyboard focus rings. |

Genuinely dynamic values (the SV/hue gradients, thumb positions, and per-channel slider gradients) are computed and applied as inline styles, so they are not themeable via CSS (they reflect the live color).

Part-level classes

For structural tweaks beyond the tokens, classNames adds a class to an individual part, alongside its built-in pqc-* class:

<ColorPicker
   value={color}
   onChange={setColor}
   classNames={{ swatch: 'rounded-full', tabActive: 'my-active-tab' }}
/>

Keys: svSquare, svThumb, hueBar, hueThumb, alphaBar, alphaThumb, tabList, tab, tabActive (added to the active tab, on top of tab), slider, sliderThumb, swatch. The dynamic inline styles above still win over your class for those specific properties (gradients, thumb positions).

License

MIT