Forms

Color Slider

Single-channel color slider built on react-aria-components, styled with the quebi design system. Adjusts one channel (hue, saturation, lightness, or alpha) along a live gradient track with a draggable thumb and optional value output.

forminputcolorsliderinteractive

Hue

Adjust a single channel along a live gradient track.

Hue
200°

Alpha

The checkerboard shows transparency through the alpha channel.

Alpha
50%

Disabled

Hue
280°

Vertical

Composed from track, thumb, and output sub-components.

140°

Controlled

Saturation
100%

hsl(200, 100%, 50%)

Source

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import type {
  ColorSliderProps as PrimitiveColorSliderProps,
  SliderOutputProps,
  SliderTrackProps,
} from "react-aria-components"
import {
  ColorSlider as PrimitiveColorSlider,
  ColorThumb,
  SliderOutput,
  SliderTrack,
} from "react-aria-components"
import { cn } from "@/lib/utils"

/**
 * ColorSlider — quebi design system
 *
 * Built on react-aria-components. A single-channel color slider (hue,
 * saturation, lightness, alpha, …) with a live gradient track, an optional
 * label/output row, and a self-contained draggable thumb. The gradient track
 * is user data; the chrome (label, output, focus ring, thumb) is restyled to
 * quebi tokens.
 */
export interface ColorSliderProps extends PrimitiveColorSliderProps {
  /** Optional eyebrow label rendered above the track. */
  label?: React.ReactNode
}

export function ColorSlider({ className, label, children, ...props }: ColorSliderProps) {
  return (
    <PrimitiveColorSlider
      data-slot="control"
      className={cn(
        "orientation-vertical:flex orientation-horizontal:grid orientation-horizontal:w-full",
        "grid-cols-[1fr_auto] flex-col items-center gap-2",
        className,
      )}
      {...props}
    >
      {children ?? (
        <>
          {label != null && (
            <span className="quebi-eyebrow orientation-vertical:hidden col-span-2 self-start">
              {label}
            </span>
          )}
          <ColorSliderTrack>
            <ColorSliderThumb />
          </ColorSliderTrack>
          <ColorSliderOutput />
        </>
      )}
    </PrimitiveColorSlider>
  )
}

export function ColorSliderOutput({ className, ...props }: SliderOutputProps) {
  return (
    <SliderOutput
      className={cn(
        "orientation-vertical:hidden font-medium text-sm text-quebi-fg-muted",
        className,
      )}
      {...props}
    />
  )
}

export function ColorSliderTrack({ className, ...props }: SliderTrackProps) {
  return (
    <SliderTrack
      className={cn(
        "group col-span-2 rounded-quebi-sm border border-cyan-500/10",
        "orientation-horizontal:h-6 orientation-horizontal:w-full",
        "orientation-vertical:ms-[50%] orientation-vertical:h-56 orientation-vertical:w-6 orientation-vertical:-translate-x-[50%]",
        "disabled:opacity-50 forced-colors:bg-[GrayText]",
        className,
      )}
      {...props}
      style={({ defaultStyle, isDisabled }) => ({
        ...defaultStyle,
        background: isDisabled
          ? undefined
          : `${defaultStyle.background}, repeating-conic-gradient(#262b30 0% 25%, #1a1e22 0% 50%) 50% / 16px 16px`,
      })}
    />
  )
}

export function ColorSliderThumb({ className }: { className?: string }) {
  return (
    <ColorThumb
      className={cn(
        "top-[50%] size-5 rounded-full border-2 border-white",
        "shadow-quebi-glow transition-[box-shadow] duration-150",
        "data-[focus-visible]:ring-2 data-[focus-visible]:ring-quebi-brand/50 data-[focus-visible]:ring-offset-2 data-[focus-visible]:ring-offset-quebi-bg",
        "data-[dragging]:scale-110",
        className,
      )}
    />
  )
}