Skip to content

ColorSlider

A 1D slider component for adjusting a single color channel, with a gradient track that reflects the current color.

Preview

Source code
tsx
import "internationalized-color/css";
import { ColorSlider, useColor } from "@urcolor/react";

export default function ColorSliderHue() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  return (
    <ColorSlider.Root
      value={color}
      onValueChange={setColor}
      colorSpace="hsl"
      channel="h"
      className="w-full"
    >
      <ColorSlider.Control>
        <ColorSlider.Track className="relative h-5 overflow-hidden rounded-xl">
          <ColorSlider.Gradient
            className="absolute inset-0 rounded-xl"
            colors={["red", "yellow", "lime", "cyan", "blue", "magenta", "red"]}
          />
          <ColorSlider.Thumb
            className="
              block size-5 rounded-full border-[2.5px] border-white bg-white
              shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
              focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
            "
            aria-label="Hue"
          />
        </ColorSlider.Track>
      </ColorSlider.Control>
    </ColorSlider.Root>
  );
}

Anatomy

tsx
<ColorSlider.Root>
  <ColorSlider.Control>
    <ColorSlider.Track>
      <ColorSlider.Checkerboard />
      <ColorSlider.Gradient />
      <ColorSlider.Thumb />
    </ColorSlider.Track>
  </ColorSlider.Control>
</ColorSlider.Root>

Examples

Hue

Source code
tsx
import "internationalized-color/css";
import { ColorSlider, useColor } from "@urcolor/react";

export default function ColorSliderHue() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  return (
    <ColorSlider.Root
      value={color}
      onValueChange={setColor}
      colorSpace="hsl"
      channel="h"
      className="w-full"
    >
      <ColorSlider.Control>
        <ColorSlider.Track className="relative h-5 overflow-hidden rounded-xl">
          <ColorSlider.Gradient
            className="absolute inset-0 rounded-xl"
            colors={["red", "yellow", "lime", "cyan", "blue", "magenta", "red"]}
          />
          <ColorSlider.Thumb
            className="
              block size-5 rounded-full border-[2.5px] border-white bg-white
              shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
              focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
            "
            aria-label="Hue"
          />
        </ColorSlider.Track>
      </ColorSlider.Control>
    </ColorSlider.Root>
  );
}

Saturation

Source code
tsx
import { useMemo } from "react";
import "internationalized-color/css";
import { getChannelConfig } from "@urcolor/core";
import { ColorSlider, useColor } from "@urcolor/react";

export default function ColorSliderSaturation() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  const gradientColors = useMemo(() => {
    const cfg = getChannelConfig("hsl", "s");
    if (!cfg) return ["gray", "blue"];
    const steps = 7;
    const colors: string[] = [];
    const cMin = cfg.culoriMin ?? cfg.min;
    const cMax = cfg.culoriMax ?? cfg.max;
    for (let i = 0; i < steps; i++) {
      const t = i / (steps - 1);
      const val = cMin + t * (cMax - cMin);
      colors.push(color.set({ mode: "hsl", s: val })?.toString() ?? "black");
    }
    return colors;
  }, [color]);

  return (
    <ColorSlider.Root
      value={color}
      onValueChange={setColor}
      colorSpace="hsl"
      channel="s"
      className="w-full"
    >
      <ColorSlider.Control>
        <ColorSlider.Track className="relative h-5 overflow-hidden rounded-xl">
          <ColorSlider.Gradient
            className="absolute inset-0 rounded-xl"
            colors={gradientColors}
          />
          <ColorSlider.Thumb
            className="
              block size-5 rounded-full border-[2.5px] border-white bg-white
              shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
              focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
            "
            aria-label="Saturation"
          />
        </ColorSlider.Track>
      </ColorSlider.Control>
    </ColorSlider.Root>
  );
}

Lightness

Source code
tsx
import { useMemo } from "react";
import "internationalized-color/css";
import { getChannelConfig } from "@urcolor/core";
import { ColorSlider, useColor } from "@urcolor/react";

export default function ColorSliderLightness() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  const gradientColors = useMemo(() => {
    const cfg = getChannelConfig("hsl", "l");
    if (!cfg) return ["black", "white"];
    const steps = 7;
    const colors: string[] = [];
    const cMin = cfg.culoriMin ?? cfg.min;
    const cMax = cfg.culoriMax ?? cfg.max;
    for (let i = 0; i < steps; i++) {
      const t = i / (steps - 1);
      const val = cMin + t * (cMax - cMin);
      colors.push(color.set({ mode: "hsl", l: val })?.toString() ?? "black");
    }
    return colors;
  }, [color]);

  return (
    <ColorSlider.Root
      value={color}
      onValueChange={setColor}
      colorSpace="hsl"
      channel="l"
      className="w-full"
    >
      <ColorSlider.Control>
        <ColorSlider.Track className="relative h-5 overflow-hidden rounded-xl">
          <ColorSlider.Gradient
            className="absolute inset-0 rounded-xl"
            colors={gradientColors}
          />
          <ColorSlider.Thumb
            className="
              block size-5 rounded-full border-[2.5px] border-white bg-white
              shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
              focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
            "
            aria-label="Lightness"
          />
        </ColorSlider.Track>
      </ColorSlider.Control>
    </ColorSlider.Root>
  );
}

Vertical

Source code
tsx
import "internationalized-color/css";
import { ColorSlider, useColor } from "@urcolor/react";

export default function ColorSliderVertical() {
  const { color, setColor } = useColor("hsl(210, 80%, 50%)");

  return (
    <ColorSlider.Root
      value={color}
      onValueChange={setColor}
      colorSpace="hsl"
      channel="h"
      orientation="vertical"
      className="h-[150px] w-auto"
    >
      <ColorSlider.Control>
        <ColorSlider.Track className="relative h-full w-5 overflow-hidden rounded-xl">
          <ColorSlider.Gradient
            className="absolute inset-0 rounded-xl"
            colors={["red", "yellow", "lime", "cyan", "blue", "magenta", "red"]}
            angle={180}
          />
          <ColorSlider.Thumb
            className="
              block size-5 rounded-full border-[2.5px] border-white bg-white
              shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_2px_4px_rgba(0,0,0,0.3)]
              focus-visible:shadow-[0_0_0_1px_rgba(0,0,0,0.3),0_0_0_3px_rgba(66,153,225,0.6)]
            "
            aria-label="Hue (vertical)"
          />
        </ColorSlider.Track>
      </ColorSlider.Control>
    </ColorSlider.Root>
  );
}

API Reference

ColorSlider.Root

The root container that manages slider state and color channel binding.

PropTypeDefaultDescription
valueColor | string | nullControlled color value.
defaultValueColor | string | nullInitial color when uncontrolled.
colorSpacestring'hsl'Color space mode (e.g. 'hsl', 'oklch').
channelstring'h'Channel to control (e.g. 'h', 's', 'l').
disabledbooleanfalseDisables interaction.
dir'ltr' | 'rtl'Reading direction.
invertedbooleanfalseVisually invert the slider.
orientation'horizontal' | 'vertical''horizontal'Slider orientation.
onValueChange(color: Color) => voidCalled when color changes.
onValueCommit(color: Color) => voidCalled when interaction ends.

ColorSlider.Control

The clickable, interactive area that handles pointer events for dragging.

ColorSlider.Track

The track area that contains the gradient and thumb.

ColorSlider.Gradient

Renders a gradient canvas background for the slider track.

PropTypeDefaultDescription
colorsstring[]Array of color stops (minimum 2).
anglenumberGradient angle in degrees.
interpolationSpacestringColor space for perceptual interpolation (e.g. 'oklch').
channelOverridesRecord<string, number> | false{ alpha: 1 }Lock specific channels to fixed values in the gradient. Set to false to reflect all channels from current color including alpha.

ColorSlider.Checkerboard

Renders a checkerboard pattern behind the gradient to visualize alpha transparency.

ColorSlider.Thumb

The draggable thumb element.

ColorSlider.Range

The filled range portion of the track.

Accessibility

ColorSlider provides a standard slider interface with robust screen reader support.

ARIA Labels

AttributeDescription
aria-labelLabels the slider with the controlled channel name.
role="slider"Applied to the thumb element for screen reader recognition.
aria-valuemin / aria-valuemaxDefines the channel's value range.
aria-valuenowCurrent value of the channel.
aria-orientationReflects horizontal or vertical orientation.

Keyboard Navigation

KeyAction
Arrow Left / Arrow DownDecrease by one step
Arrow Right / Arrow UpIncrease by one step
Shift + ArrowMove by 10 steps
HomeMove to minimum
EndMove to maximum
Page UpIncrease by large step
Page DownDecrease by large step