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

@xsolla/xui-gradient-picker

v0.214.0

Published

A cross-platform React gradient picker: a live preview track with draggable colour stops, per-stop colour and opacity editing via ColorPicker, and CSS-compatible gradient output.

Readme

Gradient Picker

A cross-platform React gradient picker: a live preview track with draggable colour stops, per-stop colour and opacity editing via ColorPicker, and CSS-compatible gradient output.

A compound component for defining a CSS-style colour gradient. Combines a visual gradient preview track with draggable stop markers and integrates with ColorPicker to edit the colour and opacity of each stop individually. Supports three gradient types and up to five colour stops.

When to use

  • When a user needs to define a multi-colour gradient — background fills, banner overlays, avatar tints, UI theme customisation
  • In design tool contexts, theme editors, or advanced colour configuration panels where fine-grained gradient control is required
  • When the product supports gradient fills as a first-class input (e.g. a game customisation screen, a chart background picker)

When not to use

  • When a solid colour is sufficient — use ColorPicker instead
  • When only a simple two-colour linear gradient with fixed endpoints is needed and the user doesn't need to adjust stops — consider a simpler two-swatch input
  • In contexts where gradient complexity would overwhelm the user — hide behind an "Advanced" toggle

Content guidelines

  • Gradient type labels — use the standard CSS-adjacent names: "Linear", "Radial", "Angular" (or "Conic" if the product uses that term). Do not use internal technical names.
  • Stop position display — when a stop is active, show its position as a percentage (e.g. 0%, 50%, 100%) in a read-only or editable label near the marker or in the ColorPicker header. This helps users set precise positions numerically.
  • Minimum stops warning — if the user tries to remove the last stop, show a tooltip or brief inline message: "A gradient requires at least one stop" rather than silently blocking the action.

Behaviour guidelines

  • Adding stops — clicking on an empty area of the stop track adds a new stop at that position, interpolating its initial colour from the gradient at that point. The new stop immediately becomes Active. Adding is only permitted up to the product-defined maximum (5 in Figma).
  • Removing stops — a stop can be removed by dragging it off the track (up or down beyond a threshold) or via a delete action (e.g. pressing Delete / Backspace when the stop is active). A minimum of 1 stop must always remain; do not allow removing the last stop.
  • Dragging stops — a stop is dragged horizontally along the track. Position is expressed as a percentage (0%–100%). Stops cannot be dragged past each other — they swap order when they cross. Provide a smooth position update with no snap unless the product explicitly requires snapping to percentage increments.
  • Active stop ColorPicker — the ColorPicker panel opens below the component when a stop is activated. If the panel would push the component out of the viewport, position it above the track instead. Closing the ColorPicker (clicking outside, pressing Escape) deactivates the stop.
  • Gradient type change — switching Gradient type via the Select applies immediately to the preview without resetting stop positions or colours.
  • Angle / centre point — for Linear, the gradient angle should be adjustable (e.g. via a degree input or a rotation handle on the preview). For Radial, the centre point should be configurable. For Angular, the rotation angle and start point should be configurable. These controls are not shown in the Figma component directly but are part of the full implementation.
  • Real-time preview — every interaction (dragging a stop, changing a colour, switching gradient type, adjusting angle) must update the gradient preview in real time without requiring a confirm action.
  • Output format — the component outputs a CSS-compatible gradient string that can be applied directly as a background or background-image value. Provide a copy action to let the user copy the gradient string.

Accessibility

  • The stop track must be operable by keyboard. When a stop has focus, ← / → arrows move it by 1% increments; Shift+← / Shift+→ move it by 10% increments.
  • Each .GradientStop must have role="slider" with aria-label identifying the stop (e.g. aria-label="Stop 1 position") and aria-valuenow / aria-valuemin="0" / aria-valuemax="100".
  • The gradient type selector must have aria-label="Gradient type".
  • When a stop is activated and the ColorPicker opens, focus must move into the ColorPicker. When the ColorPicker closes, focus must return to the stop marker.
  • The gradient preview is decorative — it must have aria-hidden="true" and not receive focus.
  • Adding a stop via keyboard: provide a button (e.g. "+ Add stop") as a keyboard-accessible alternative to clicking on the track.
  • Removing a stop via keyboard: when a stop is active, Delete / Backspace removes it and focus moves to the adjacent stop or the track.
  • Do not rely on the visual gradient colour alone to communicate stop positions — always show numeric position values for screen reader users and users who cannot perceive colour differences in the gradient.

Gradient types

The angle can only be controlled for linear gradients and angular gradients.

Stop hover

While dragging a gradient stop, the row of the corresponding stop transitions into the active state.

Installation

npm install @xsolla/xui-gradient-picker

Demo

Basic Gradient Picker

import * as React from "react";
import { GradientPicker } from "@xsolla/xui-gradient-picker";

export default function BasicGradientPicker() {
  return <GradientPicker onChange={({ css }) => console.log(css)} />;
}

Controlled Gradient Picker

import * as React from "react";
import {
  GradientPicker,
  DEFAULT_GRADIENT,
  toCssGradient,
  type GradientValue,
} from "@xsolla/xui-gradient-picker";

export default function ControlledGradientPicker() {
  const [gradient, setGradient] = React.useState<GradientValue>(
    DEFAULT_GRADIENT
  );

  return (
    <>
      <GradientPicker
        value={gradient}
        onChange={({ value }) => setGradient(value)}
      />
      <div style={{ backgroundImage: toCssGradient(gradient) }} />
    </>
  );
}

Radial Gradient

import * as React from "react";
import { GradientPicker } from "@xsolla/xui-gradient-picker";

export default function RadialGradient() {
  return (
    <GradientPicker
      defaultValue={{
        type: "radial",
        angle: 0,
        stops: [
          { id: "stop-1", color: "#D9D9D9", position: 0, opacity: 100 },
          { id: "stop-2", color: "#22A8C3", position: 100, opacity: 100 },
        ],
      }}
    />
  );
}

Radial gradients have no rotation, so the angle field is hidden for them.

Angular ("Conic") Gradient

import * as React from "react";
import { GradientPicker } from "@xsolla/xui-gradient-picker";

export default function AngularGradient() {
  return (
    <GradientPicker
      typeLabels={{ angular: "Conic gradient" }}
      onChange={({ css }) => console.log(css)}
    />
  );
}

Anatomy

import { GradientPicker } from "@xsolla/xui-gradient-picker";

<GradientPicker
  value={gradient} // Controlled gradient value
  defaultValue={initial} // Initial value for uncontrolled usage
  onChange={handleChange} // ({ value, css }) => void
  gradientTypes={["linear", "radial", "angular"]} // Types offered in the selector
  typeLabels={{ angular: "Conic gradient" }} // Option label overrides
  minStops={1} // Floor for removal
  maxStops={5} // Ceiling for adding
  disabled={false} // Disable every control
  stopsLabel="Stops" // Heading above the stop list
/>;

API Reference

GradientPicker

GradientPickerProps:

| Prop | Type | Default | Description | | :------------- | :---------------------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------ | | testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. | | value | GradientValue | — | Controlled gradient value. Pair with onChange. | | defaultValue | GradientValue | DEFAULT_GRADIENT | Initial gradient value for uncontrolled usage. | | onChange | (event: GradientPickerChangeEvent) => void | — | Fired on every interaction. Receives the new value and its CSS string. | | gradientTypes | GradientType[] | ["linear", "radial", "angular"] | Gradient types offered in the selector, in display order. | | typeLabels | Partial<Record<GradientType, string>> | — | Overrides for the gradient-type option labels. | | minStops | number | 1 | Smallest number of stops the user may reduce the gradient to. | | maxStops | number | 5 | Largest number of stops the user may add. | | disabled | boolean | false | Disables every control and blocks track interaction. | | typeAriaLabel | string | "Gradient type" | Accessible name for the gradient-type selector. | | angleAriaLabel | string | "Gradient angle" | Accessible name for the angle field. | | stopsLabel | string | "Stops" | Heading rendered above the stop list. |

Types

type GradientType = "linear" | "radial" | "angular";

interface GradientStop {
  id: string; // Stable identity, survives reordering
  color: string; // "#RRGGBB"
  position: number; // 0–100 (percent)
  opacity: number; // 0–100 (percent)
}

interface GradientValue {
  type: GradientType;
  angle: number; // Degrees; ignored for "radial"
  stops: GradientStop[];
}

interface GradientPickerChangeEvent {
  value: GradientValue;
  css: string; // e.g. "linear-gradient(0deg, #D9D9D9 0%, #22A8C3 100%)"
}

Exported helpers

Every gradient operation is a pure function, so a consuming surface can render or transform a gradient without mounting the component.

| Helper | Signature | Description | | :------------------------------------ | :--------------------------------------------------------------- | :-------------------------------------------------------------------------------- | | toCssGradient(value) | (value: GradientValue) => string | Serialises to linear-gradient() / radial-gradient() / conic-gradient(). | | toPreviewCssGradient(value) | (value: GradientValue) => string | Left-to-right ramp used by the preview track, regardless of gradient type. | | interpolateColorAt(stops, position) | (stops: GradientStop[], position: number) => { color, opacity } | Samples the gradient — the colour a stop added at position inherits. | | addStopAt(stops, position, max) | (…) => GradientStop[] | Adds an interpolated stop; returns the input array unchanged at max. | | removeStop(stops, id, min) | (…) => GradientStop[] | Removes by id; returns the input array unchanged at min. | | updateStop(stops, id, patch) | (…) => GradientStop[] | Applies a clamped, normalised patch to one stop. | | sortStops(stops) | (stops: GradientStop[]) => GradientStop[] | Position order, stable on ties, non-mutating. | | supportsAngle(type) | (type: GradientType) => boolean | false for radial — the angle field is hidden for it. | | DEFAULT_GRADIENT | GradientValue | The two-stop #D9D9D9 → #22A8C3 linear ramp from the Figma component. |

CSS output

| Type | Output | | :-------- | :-------------------------------------------------------------- | | linear | linear-gradient(<angle>deg, <stops>) | | radial | radial-gradient(circle, <stops>) | | angular | conic-gradient(from <angle>deg, <stops>) |

A fully opaque stop serialises to its hex value; a partially transparent stop serialises to rgba(). A gradient with a single stop is emitted twice (0% and 100%) because CSS gradients require at least two colour stops.

Keyboard navigation

| Key | Action | | :------------------------ | :------------------------------------------------ | | Tab / Shift+Tab | Move focus between markers, fields and buttons | | ← / → (marker focused) | Move the stop by 1% | | Shift+← / Shift+→ | Move the stop by 10% | | Home / End | Jump the stop to 0% / 100% | | Enter / Space | Activate the stop and open its ColorPicker | | Delete / Backspace | Remove the stop (blocked at minStops) | | Escape | Close the ColorPicker; focus returns to the marker |

Theme

  • The panel background comes from the layer/float colour token (theme.colors.layer.float) so it reads as a floating surface in both light and dark mode, matching ColorPicker.
  • Corner radius comes from theme.shape.contextMenu.lg.borderRadius.
  • Stop markers use theme.colors.background.primary for their ring so the stop colour stays legible against any track fill.

Accessibility

  • Each stop marker is a role="slider" with aria-label="Stop N position", aria-valuenow, aria-valuemin="0", aria-valuemax="100" and aria-valuetext="N%".
  • The preview track is aria-hidden="true" and is not focusable — position is always available numerically from the per-stop position field and the marker's aria-valuetext.
  • The + button is the keyboard-accessible alternative to clicking the track; it drops the new stop in the widest gap.
  • Closing the ColorPicker (Escape) returns focus to the marker that opened it.
  • The angle field and every per-stop field carry an explicit aria-label.

Known gaps

These are deliberate v1 boundaries, tracked as follow-ups:

  • Type selector namingSelectProps has no aria-label, so the accessible name for the gradient-type selector is carried by a wrapping role="group" element. Once Select accepts aria-label, move it onto the combobox itself.
  • ColorPicker placement — the active-stop ColorPicker renders inline below the stop list. The spec's "flip above the track when the panel would leave the viewport" behaviour needs a floating-popover layer.
  • Pointer drag is web-only — keyboard operation works everywhere; marker dragging uses web mouse events. React Native PanResponder parity is a follow-up, matching the approach Slider takes.
  • Radial centre point / angular start point are not exposed. They are not in the Figma component; the behaviour spec lists them as part of a fuller implementation.