Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

InputsReact

Slider

An easily stylable range input.

Rendered by React 19.3.1

Loading React…
import { Slider } from '@hamur/react/slider'

export default function SliderExample() {
  return (
    <div className="stack">
      <Slider.Root className="slider" defaultValue={30}>
        <div className="slider-head">
          <Slider.Label>Volume</Slider.Label>
          <Slider.Value className="muted" />
        </div>
        <Slider.Control className="slider-control">
          <Slider.Track className="slider-track">
            <Slider.Indicator className="slider-indicator" />
            <Slider.Thumb className="slider-thumb" aria-label="Volume" />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
      <Slider.Root
        className="slider"
        defaultValue={[20, 60]}
        minStepsBetweenValues={5}
      >
        <div className="slider-head">
          <Slider.Label>Price range</Slider.Label>
          <Slider.Value className="muted" />
        </div>
        <Slider.Control className="slider-control">
          <Slider.Track className="slider-track">
            <Slider.Indicator className="slider-indicator" />
            <Slider.Thumb
              className="slider-thumb"
              index={0}
              aria-label="Minimum price"
            />
            <Slider.Thumb
              className="slider-thumb"
              index={1}
              aria-label="Maximum price"
            />
          </Slider.Track>
        </Slider.Control>
      </Slider.Root>
    </div>
  )
}
763Tests passing in React · 0 failing
16.3 KiBSize
Not measured yetMemory
Not measured yetSpeed
productdevbook and 23 upstream authors8 commits · last

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<Slider.Root>
  <Slider.Label />
  <Slider.Value />
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Examples

Range Slider

Two thumbs set a range; thumbCollisionBehavior decides what happens when they meet.

SliderRangeSlider.tsx
import { Slider } from '@hamur/react/slider'

export default function SliderRangeSliderExample() {
  return (
    <Slider.Root className="slider" defaultValue={[25, 45]}>
      <Slider.Control className="slider-control">
        <Slider.Track className="slider-track">
          <Slider.Indicator className="slider-indicator" />
          <Slider.Thumb index={0} aria-label="Minimum value" className="slider-thumb" />
          <Slider.Thumb index={1} aria-label="Maximum value" className="slider-thumb" />
        </Slider.Track>
      </Slider.Control>
    </Slider.Root>
  )
}

Steps

step snaps the value; largeStep sets the jump of PageUp, PageDown and Shift with the arrows.

SliderSteps.tsx
import { Slider } from '@hamur/react/slider'

export default function SliderStepsExample() {
  return (
    <Slider.Root
      className="slider"
      defaultValue={400}
      min={100}
      max={900}
      step={100}
      largeStep={200}
    >
      <div className="slider-head">
        <Slider.Label>Font weight</Slider.Label>
        <Slider.Value className="muted" />
      </div>
      <Slider.Control className="slider-control">
        <Slider.Track className="slider-track">
          <Slider.Indicator className="slider-indicator" />
          <Slider.Thumb className="slider-thumb" />
        </Slider.Track>
      </Slider.Control>
    </Slider.Root>
  )
}

Marks

Tick marks placed along the track, paired with step.

SliderMarks.tsx
import { Slider } from '@hamur/react/slider'

const MIN = 0
const MAX = 100
const MARKS = [0, 25, 50, 75, 100]

export default function SliderMarksExample() {
  return (
    <Slider.Root className="slider" defaultValue={40} min={MIN} max={MAX}>
      <Slider.Control className="slider-control">
        <Slider.Track className="slider-track">
          <Slider.Indicator className="slider-indicator" />
          {MARKS.map((mark) => (
            <div
              key={mark}
              aria-hidden
              className="slider-mark"
              style={{ left: `${valueToPercent(mark)}%` }}
            />
          ))}
          <Slider.Thumb aria-label="Volume" className="slider-thumb" />
        </Slider.Track>
      </Slider.Control>
      <div className="slider-mark-labels" aria-hidden>
        {MARKS.map((mark) => (
          <span
            key={mark}
            className="slider-mark-label"
            style={{ left: `${valueToPercent(mark)}%` }}
          >
            {mark}
          </span>
        ))}
      </div>
    </Slider.Root>
  )
}

function valueToPercent(value: number) {
  return ((value - MIN) / (MAX - MIN)) * 100
}

Edge Alignment

thumbAlignment keeps the thumb inside the control at min and max.

SliderEdgeAlignment.tsx
import { Slider } from '@hamur/react/slider'

export default function SliderEdgeAlignmentExample() {
  return (
    <Slider.Root className="slider" thumbAlignment="edge" defaultValue={25}>
      <Slider.Control className="slider-control">
        <Slider.Track className="slider-track">
          <Slider.Indicator className="slider-indicator" />
          <Slider.Thumb aria-label="Volume" className="slider-thumb" />
        </Slider.Track>
      </Slider.Control>
    </Slider.Root>
  )
}

Vertical

orientation makes a vertical slider.

SliderVertical.tsx
import { Slider } from '@hamur/react/slider'

export default function SliderVerticalExample() {
  return (
    <Slider.Root orientation="vertical" defaultValue={35}>
      <Slider.Control className="slider-control">
        <Slider.Track className="slider-track">
          <Slider.Indicator className="slider-indicator" />
          <Slider.Thumb aria-label="Volume" className="slider-thumb" />
        </Slider.Track>
      </Slider.Control>
    </Slider.Root>
  )
}

API reference

28 props across 7 parts, in React syntax. className, style, render are listed once, under Shared props.

ControlSlider.Control

The clickable, interactive part of the slider. Renders a <div>.

Control data attributes
Data attributeDescription
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

IndicatorSlider.Indicator

Visualizes the current value of the slider. Renders a <div>.

Indicator data attributes
Data attributeDescription
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

LabelSlider.Label

An accessible label that is automatically associated with the slider thumbs. Renders a <div>.

RootSlider.Root

Groups all parts of the slider. Renders a <div>.

Root props
PropTypeDefault
nameIdentifies the field when a form is submitted.string—
defaultValueThe uncontrolled value of the slider when it's initially rendered.Value—
valueThe value of the slider.Value—
onValueChangeCallback function that is fired when the slider's value changed.((value: Value extends number ? number : Value, eventDetails: SliderRootChangeEventDetails) => void)—
onValueCommittedCallback function that is fired when a value change is committed.((value: Value extends number ? number : Value, eventDetails: SliderRootCommitEventDetails) => void)—
formIdentifies the form that owns the slider inputs.string—
localeThe locale used by Intl.NumberFormat when formatting the value.Intl.LocalesArgument—
thumbAlignmentHow the thumb(s) are aligned relative to Slider.Control when the value is at min or max:"center" | "edge" | "edge-client-only"'center'
thumbCollisionBehaviorControls how thumbs behave when they collide during pointer interactions."none" | "push" | "swap"'push'
stepThe granularity with which the slider can step through values.number1
largeStepThe granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.number10
minStepsBetweenValuesThe minimum steps between values in a range slider.number0
minThe minimum allowed value of the slider.number0
maxThe maximum allowed value of the slider.number100
formatOptions to format the value.Intl.NumberFormatOptions—
disabledWhether the slider should ignore user interaction.booleanfalse
orientationThe component orientation.Orientation'horizontal'
Root data attributes
Data attributeDescription
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

ThumbSlider.Thumb

The draggable part of the slider at the tip of the indicator. and a nested <input type="range">. Renders a <div>.

Thumb props
PropTypeDefault
aria-valuetextA string value forwarded to the [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the input.string—
getAriaLabelA function which returns a string value for the [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label) attribute of the input.((index: number) => string) | null—
getAriaValueTextA function which returns a string value for the [aria-valuetext](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the input.((formattedValue: string, value: number, index: number) => string) | null—
indexThe index of the thumb which corresponds to the index of its value in thenumber—
onBlurA blur handler forwarded to the input.React.FocusEventHandler<HTMLInputElement>—
onFocusA focus handler forwarded to the input.React.FocusEventHandler<HTMLInputElement>—
onKeyDownA keydown handler forwarded to the input.React.KeyboardEventHandler<HTMLInputElement>—
tabIndexOptional tab index attribute forwarded to the input.number—
disabledWhether the thumb should ignore user interaction.booleanfalse
inputRefA ref to access the nested input element.React.Ref<HTMLInputElement>—
Thumb data attributes
Data attributeDescription
data-indexIndicates the index of the thumb in range sliders.
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the thumb or the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

TrackSlider.Track

Contains the slider indicator and represents the entire range of the slider. Renders a <div>.

Track data attributes
Data attributeDescription
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

ValueSlider.Value

Displays the current value of the slider as text. Renders an <output>.

Value props
PropTypeDefault
children((formattedValues: readonly string[], values: readonly number[]) => React.ReactNode) | null—
Value data attributes
Data attributeDescription
data-draggingPresent while the user is dragging.
data-orientationIndicates the orientation of the slider.
data-disabledPresent when the slider is disabled.
data-validPresent when the slider is in a valid state (when wrapped in Field.Root).
data-invalidPresent when the slider is in an invalid state (when wrapped in Field.Root).
data-touchedPresent when the slider has been touched (when wrapped in Field.Root).
data-dirtyPresent when the slider's value has changed (when wrapped in Field.Root).
data-focusedPresent when the slider is focused (when wrapped in Field.Root).

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
ArrowRightArrowUpIncreases the value by step (ArrowLeft in right-to-left)Thumb
ArrowLeftArrowDownDecreases the value by step (ArrowRight in right-to-left)Thumb
ShiftArrowUpShiftArrowRightIncreases the value by largeStep, stopping at maxThumb
ShiftArrowDownShiftArrowLeftDecreases the value by largeStep, stopping at minThumb
PageUpIncreases the value by largeStepThumb
PageDownDecreases the value by largeStepThumb
HomeSets the value to minThumb
EndSets the value to maxThumb

Accessibility

What the parts set
PartSetsSo that
Thumba nested <input type="range"> with aria-valuenow, aria-orientationeach thumb is read as a slider with its value
Thumbaria-valuetext from getAriaValueText or formatthe value is read as written, such as "40 °C"
Thumbaria-labelledby to Label, or the name from getAriaLabeleach thumb has a name, also in a range slider
Rootrole="group", aria-labelledby to Labelthe thumbs are read as one control
Valuerenders an <output>the value text is tied to the slider without being announced on every change

Focus order

  1. Each Thumb is one tab stop.
  2. Pressing the Control focuses the thumb it moves; clicking Label focuses the thumb of a single-value slider.