Slider

A single-thumb slider for selecting a numeric value within a range. It is built on top of the Range component, and exposes the same behavior through a single-value API.

import { Slider } from "@tenstorrent/vesper/slider";

export default function SliderDemo() {
  return <Slider thumbAriaLabel="Volume" defaultValue={50} />;
}

Options

PropTypeDescriptionDefault
thumbAriaLabelstringThe accessible aria-label for the thumb. Required.—
valuenumberThe value of the thumb (controlled mode).—
defaultValuenumberThe initial thumb value (uncontrolled mode).min
onValueChange(value: number) => voidCallback fired as the thumb value changes during interaction.—
onValueCommit(value: number) => voidCallback fired when the thumb interaction is completed, eg. on pointer up.—
minnumberThe minimum allowed value.0
maxnumberThe maximum allowed value.100
stepnumberThe stepping interval between selectable values.1
showTicksbooleanWhen true, renders tick marks along the track at each step interval.false
showValueLabelbooleanWhen true, displays a value label above the thumb.false
valueLabelstringA custom display label for the thumb. When unset, it falls back to the thumb's current value.—
disabledbooleanWhen true, prevents interaction.false
namestringThe form field name submitted with form data.—
formstringAssociates the slider with a <form> element by its id.—

All other props are forwarded to the wrapping <div> element.

If you need to select a span of values rather than a single one, reach for the Range component instead: it renders the same track, but with a thumb per value.

Examples

Uncontrolled vs controlled

Render a Slider in an uncontrolled fashion to let it keep track of its own value. Pass defaultValue if the thumb should start somewhere other than min:

import { Slider } from "@tenstorrent/vesper/slider";

export default function UncontrolledSlider() {
  return <Slider defaultValue={30} thumbAriaLabel="Volume" />;
}

If you need to control the value, use the value and onValueChange props. onValueChange fires continuously as the thumb is dragged:

import { useState } from "react";

import { Slider } from "@tenstorrent/vesper/slider";

export default function ControlledSlider() {
  const [volume, setVolume] = useState(75);

  return (
    <Slider value={volume} onValueChange={setVolume} thumbAriaLabel="Volume" />
  );
}

Use onValueCommit when you only care about the final value of an interaction, eg. to avoid firing a request on every pointer move:

import { Slider } from "@tenstorrent/vesper/slider";

export default function VolumeSlider() {
  return (
    <Slider
      defaultValue={50}
      onValueCommit={(volume) => savePreference({ volume })}
      thumbAriaLabel="Volume"
    />
  );
}

Bounds and stepping

min, max, and step describe the values the thumb can take, defaulting to 0, 100, and 1. A step of 100 on a range of 0-1000 restricts the thumb to multiples of 100:

import { Slider } from "@tenstorrent/vesper/slider";

export default function LargeStepSlider() {
  return (
    <Slider
      min={0}
      max={1000}
      step={100}
      defaultValue={500}
      showValueLabel
      thumbAriaLabel="Budget"
    />
  );
}

The step also determines how far the thumb moves per arrow key press when it is focused.

When neither value nor defaultValue is provided, the thumb starts at min rather than at zero, so a slider with a min of 10 starts at 10.

Showing ticks

Pass showTicks to mark each step along the track. Ticks are rendered between min and max, so a range of 0-10 with a step of 1 renders nine of them:

import { Slider } from "@tenstorrent/vesper/slider";

export default function SliderWithTicks() {
  return (
    <Slider
      min={0}
      max={10}
      step={1}
      showTicks
      defaultValue={3}
      thumbAriaLabel="Rating"
    />
  );
}
Ticks are drawn per step, so a fine step on a wide range produces a dense, noisy track. Ticks work best when the range has a handful of stops, eg. a range of 0-100 with a step of 10.

Value label

Pass showValueLabel to render the thumb's current value above it as you drag:

import { Slider } from "@tenstorrent/vesper/slider";

export default function SliderWithVisibleValueLabel() {
  return <Slider showValueLabel defaultValue={40} thumbAriaLabel="Volume" />;
}

Use valueLabel to display something other than the raw value, such as a formatted or unit-suffixed one. It is only rendered when showValueLabel is also passed:

import { useState } from "react";

import { Slider } from "@tenstorrent/vesper/slider";

export default function SliderWithCustomValueLabel() {
  const [volume, setVolume] = useState(40);

  return (
    <Slider
      showValueLabel
      value={volume}
      onValueChange={setVolume}
      valueLabel={`${volume}%`}
      thumbAriaLabel="Volume"
    />
  );
}
Value label is presentational, and is not announced by assistive technology. Screen readers announce the underlying numeric value of a thumb instead. If a label carries meaning that the number alone doesn't, such as a currency or a unit, include it in the thumbAriaLabel too.

Keyboard interaction

The thumb is a native <input type="range">, so a Slider is fully keyboard operable once it is focused:

KeyBehaviour
ArrowRight / ArrowUpIncreases the value by step.
ArrowLeft / ArrowDownDecreases the value by step.
Shift + any arrow keyMoves the thumb by 10 units instead of step.
PageUp / PageDownIncreases / decreases the value by 10 units.
HomeMoves the thumb to min.
EndMoves the thumb to max.

Disabling the slider

Pass disabled to prevent all interaction. A disabled Slider still displays its current value, but its thumb is skipped in the tab order and excluded from form data:

import { Slider } from "@tenstorrent/vesper/slider";

export default function DisabledSlider() {
  return <Slider disabled defaultValue={50} thumbAriaLabel="Volume" />;
}

Usage in forms

The thumb renders a native <input type="range">, so passing a name includes its value in form submissions. Use the form prop to associate the field with a <form> rendered elsewhere on the page:

import { useState } from "react";

import { Button } from "@tenstorrent/vesper/button";
import { Slider } from "@tenstorrent/vesper/slider";
import { Typography } from "@tenstorrent/vesper/typography";

export default function FormSliderDemo() {
  const [submitted, setSubmitted] = useState<string | null>(null);

  return (
    <form
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "var(--vesper-spacing-4)",
      }}
      onSubmit={(event) => {
        event.preventDefault();
        const data = new FormData(event.currentTarget);
        setSubmitted(String(data.get("volume") ?? ""));
      }}
    >
      <Slider name="volume" defaultValue={50} thumbAriaLabel="Volume" />
      <Button size="sm" type="submit">
        Submit
      </Button>
      {submitted !== null && (
        <Typography variant="copy-sm">Submitted value: {submitted}</Typography>
      )}
    </form>
  );
}

A range input always has a value, so a Slider submits its current value even if the user never touches it. The form above submits volume=50 on an untouched slider.