PixelSlider

Controlled single- or range-thumb slider with optional marks, tick grid, and per-thumb tooltips.

  • Single (value: number) and range (value: [number, number]) modes share one component
  • Seven tones via the shared toneMap palette; pixel and linear surfaces
  • Optional labeled marks and step-aligned tick grid under the track
  • Per-thumb tooltips with always / drag / never visibility modes
  • Full keyboard support: arrows, Home, End, PageUp, PageDown
Status
stable
Category
forms
Since
v1.0.0

API

import { PixelSlider } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
value (required)number | [number, number]—Controlled scalar value.
onChange (required)((next: number) => void) | ((next: [number, number]) => void)—Fires with the next scalar value.
label (required)string—Visible label rendered above the track.
minnumber0Minimum value. Default: 0.
maxnumber100Maximum value. Default: 100.
stepnumber1Distance between two values, counted from min: the slider takes min, min + step, min + 2 * step… up to max. Default: 1.
disabledbooleanfalseDisables interaction + grays out the track.
tone'green' | 'cyan' | 'gold' | 'red' | 'purple' | 'pink' | 'neutral''cyan'Visual tone for the active fill + thumb. Default: 'cyan'.
showMinMaxbooleanfalseRender min / max numbers under the track.
surface'pixel' | 'linear'—Surface variant. Inherits from PxlKitSurfaceProvider when omitted.
namestring—Form-serialization name (range mode emits name[0] / name[1]).
requiredboolean—Marks the field as required for native form validation.
idstring—DOM id forwarded to the single / lower thumb.
marksPixelSliderMark[]—Labeled marks plotted on the track.
showTooltip'always' | 'drag' | 'never''never'When/how to show a value tooltip on each thumb. - 'always': visible all the time - 'drag': visible while dragging or focused - 'never': hidden (default)
ticksbooleanfalseRender tick marks under the track for every discrete step.

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • slider

Each thumb is rendered with role="slider" and exposes aria-valuemin, aria-valuemax, aria-valuenow, and an accessible label derived from the label prop. In range mode the lower thumb is constrained ≤ upper thumb on every change. Pointer interaction uses setPointerCapture so dragging tracks the cursor even when it leaves the track.

Keyboard

KeyActionWhen
ArrowRight / ArrowUpIncrement the focused thumb by step.—
ArrowLeft / ArrowDownDecrement the focused thumb by step.—
HomeJump the focused thumb to min.—
EndJump the focused thumb to max.—
PageUpIncrement the focused thumb by step * 10.—
PageDownDecrement the focused thumb by step * 10.—
TabMove focus to the next thumb or focusable element.—

Usage

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = useState(40);
  return (
    <PixelSlider
      label="Volume"
      value={value}
      onChange={setValue}
    />
  );
}

Examples

Default

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Default() {
  const [value, setValue] = useState(40);
  return (
    <PixelSlider
      label="Volume"
      value={value}
      onChange={setValue}
    />
  );
}

Range

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Range() {
  const [value, setValue] = useState<[number, number]>([20, 80]);
  return (
    <PixelSlider
      label="Price range"
      value={value}
      onChange={setValue}
    />
  );
}

Tones

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Tones() {
  const [neutral, setNeutral] = useState(40);
  const [green, setGreen] = useState(50);
  const [cyan, setCyan] = useState(60);
  const [gold, setGold] = useState(70);
  const [red, setRed] = useState(30);
  const [purple, setPurple] = useState(45);
  const [pink, setPink] = useState(55);
  return (
    <div className="space-y-4">
      <PixelSlider label="Neutral" tone="neutral" value={neutral} onChange={setNeutral} />
      <PixelSlider label="Green" tone="green" value={green} onChange={setGreen} />
      <PixelSlider label="Cyan" tone="cyan" value={cyan} onChange={setCyan} />
      <PixelSlider label="Gold" tone="gold" value={gold} onChange={setGold} />
      <PixelSlider label="Red" tone="red" value={red} onChange={setRed} />
      <PixelSlider label="Purple" tone="purple" value={purple} onChange={setPurple} />
      <PixelSlider label="Pink" tone="pink" value={pink} onChange={setPink} />
    </div>
  );
}

Surfaces

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Surfaces() {
  const [pixel, setPixel] = useState(40);
  const [linear, setLinear] = useState(60);
  return (
    <div className="space-y-4">
      <PixelSlider label="Pixel surface" surface="pixel" value={pixel} onChange={setPixel} />
      <PixelSlider label="Linear surface" surface="linear" value={linear} onChange={setLinear} />
    </div>
  );
}

With min/max

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function WithMinMax() {
  const [value, setValue] = useState(75);
  return (
    <PixelSlider
      label="Brightness"
      min={0}
      max={200}
      step={5}
      showMinMax
      value={value}
      onChange={setValue}
    />
  );
}

With marks

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function WithMarks() {
  const [value, setValue] = useState(50);
  return (
    <PixelSlider
      label="Quality"
      value={value}
      onChange={setValue}
      marks={[
        { value: 0, label: 'Low' },
        { value: 25, label: 'Med' },
        { value: 50, label: 'High' },
        { value: 75, label: 'Ultra' },
        { value: 100, label: 'Max' },
      ]}
    />
  );
}

With ticks

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function WithTicks() {
  const [value, setValue] = useState(40);
  return (
    <PixelSlider
      label="Step ticks"
      min={0}
      max={100}
      step={10}
      ticks
      value={value}
      onChange={setValue}
    />
  );
}

Always tooltip

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function WithTooltip() {
  const [value, setValue] = useState(60);
  return (
    <PixelSlider
      label="Always tooltip"
      value={value}
      onChange={setValue}
      showTooltip="always"
    />
  );
}

Tooltip on drag

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function TooltipOnDrag() {
  const [value, setValue] = useState(35);
  return (
    <PixelSlider
      label="Drag tooltip"
      value={value}
      onChange={setValue}
      showTooltip="drag"
    />
  );
}

Disabled

import { PixelSlider } from '@pxlkit/ui-kit';

export function Disabled() {
  return (
    <PixelSlider
      label="Disabled"
      value={50}
      onChange={() => {}}
      disabled
    />
  );
}

Required

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function Required() {
  const [value, setValue] = useState(30);
  return (
    <PixelSlider
      label="Required setting"
      value={value}
      onChange={setValue}
      required
      name="setting"
    />
  );
}

Range with marks

import { useState } from 'react';
import { PixelSlider } from '@pxlkit/ui-kit';

export function RangeWithMarks() {
  const [value, setValue] = useState<[number, number]>([30, 70]);
  return (
    <PixelSlider
      label="Filter range"
      value={value}
      onChange={setValue}
      showMinMax
      showTooltip="always"
      marks={[
        { value: 0, label: '0' },
        { value: 50, label: '50' },
        { value: 100, label: '100' },
      ]}
    />
  );
}

Related