ShardsUI is in beta. APIs may change before 1.0.

Slider

A value picked along a range.

Brightness
40
<script lang="ts">
  import { Slider } from '@shardsui/svelte/slider'
</script>

<Slider.Root value={40} class="w-56">
  <div class="flex items-center justify-between">
    <Slider.Label class="text-sm font-medium text-gray-900 select-none">Brightness</Slider.Label>
    <Slider.Value class="text-sm text-gray-600 select-none" />
  </div>
  <Slider.Control class="flex w-full touch-none items-center py-3 select-none">
    <Slider.Track class="h-1 w-full rounded-sm bg-gray-50 inset-ring inset-ring-gray-200">
      <Slider.Indicator class="rounded-sm bg-gray-700" />
      <Slider.Thumb
        class="size-4 rounded-full bg-gray-50 outline-1 outline-gray-300 has-focus-visible:outline-2 has-focus-visible:outline-gray-950"
      />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Anatomy

<script>
  import { Slider } from '@shardsui/svelte/slider'
</script>

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

Usage guidelines

  • Form controls must have an accessible name: a <Slider.Label> usually does the job; when the design has no visible label, give each <Slider.Thumb> its own aria-label instead. See Labeling a slider and the forms guide.

Examples

Range slider

Build a range slider in two steps:

  1. Pass an array of values, and render one <Slider.Thumb> per value in that array
  2. For server-side rendering, also give each thumb a numeric index matching the position of its value in the array

When two thumbs meet under the pointer, thumbCollisionBehavior on <Slider.Root> decides what happens next.

Price range
$40.00 – $80.00
<script lang="ts">
  import { Slider } from '@shardsui/svelte/slider'
</script>

<Slider.Root value={[40, 80]} format={{ style: 'currency', currency: 'USD' }} class="w-56">
  <div class="flex items-center justify-between">
    <Slider.Label class="text-sm font-medium text-gray-900 select-none">Price range</Slider.Label>
    <Slider.Value class="text-sm text-gray-600 select-none" />
  </div>
  <Slider.Control class="flex w-full touch-none items-center py-3 select-none">
    <Slider.Track class="h-1 w-full rounded-sm bg-gray-50 inset-ring inset-ring-gray-200">
      <Slider.Indicator class="rounded-sm bg-gray-700" />
      <Slider.Thumb
        index={0}
        aria-label="Minimum price"
        class="size-4 rounded-full bg-gray-50 outline-1 outline-gray-300 has-focus-visible:outline-2 has-focus-visible:outline-gray-950"
      />
      <Slider.Thumb
        index={1}
        aria-label="Maximum price"
        class="size-4 rounded-full bg-gray-50 outline-1 outline-gray-300 has-focus-visible:outline-2 has-focus-visible:outline-gray-950"
      />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Formatting the value

format and locale reach both <Slider.Value> and the thumbs' aria-valuetext. Without a children snippet, Value renders the formatted values joined by an en dash; pass one to compose the text yourself:

<Slider.Value>
  {#snippet children(formattedValues)}
    {formattedValues[0]} to {formattedValues[1]}
  {/snippet}
</Slider.Value>

Thumb alignment

With the default "center" alignment, a thumb at min or max overhangs the ends of the control. With thumbAlignment="edge", the thumb is inset so its edge lines up with the control's edge, keeping it fully inside.

<script lang="ts">
  import { Slider } from '@shardsui/svelte/slider'
</script>

<Slider.Root thumbAlignment="edge" value={100} class="w-56">
  <Slider.Control class="flex w-full touch-none items-center py-3 select-none">
    <Slider.Track class="h-1 w-full rounded-sm bg-gray-50 inset-ring inset-ring-gray-200">
      <Slider.Indicator class="rounded-sm bg-gray-700" />
      <Slider.Thumb
        aria-label="Opacity"
        class="size-4 rounded-full bg-gray-50 outline-1 outline-gray-300 has-focus-visible:outline-2 has-focus-visible:outline-gray-950"
      />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Labeling a slider

When a single-thumb slider has no visible label — a volume control, say — put an aria-label on the <Slider.Thumb>:

<Slider.Root>
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb aria-label="Volume" />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

If the label should be visible instead, render <Slider.Label>:

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

A multi-thumb range slider needs both: keep the visible <Slider.Label>, and give each <Slider.Thumb> its own aria-label so screen readers can tell one thumb from another:

<Slider.Root value={[25, 75]}>
  <Slider.Label>Price range</Slider.Label>
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb index={0} aria-label="Minimum price" />
      <Slider.Thumb index={1} aria-label="Maximum price" />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Vertical

For a vertical slider, pass orientation="vertical" to <Slider.Root>.

<script lang="ts">
  import { Slider } from '@shardsui/svelte/slider'
</script>

<Slider.Root orientation="vertical" value={60}>
  <Slider.Control class="flex h-32 touch-none px-3 select-none">
    <Slider.Track class="h-full w-1 rounded-sm bg-gray-50 inset-ring inset-ring-gray-200">
      <Slider.Indicator class="rounded-sm bg-gray-700" />
      <Slider.Thumb
        aria-label="Volume"
        class="size-4 rounded-full bg-gray-50 outline-1 outline-gray-300 has-focus-visible:outline-2 has-focus-visible:outline-gray-950"
      />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Form integration

Give <Slider.Root> a name and its value is submitted with the surrounding form:

<Form>
  <Slider.Root name="volume">
    <Slider.Label>Volume</Slider.Label>
    <Slider.Control>
      <Slider.Track>
        <Slider.Indicator />
        <Slider.Thumb />
      </Slider.Track>
    </Slider.Control>
  </Slider.Root>
</Form>

For a grouped multi-thumb range slider in a form, nest it in a Fieldset: the legend names the group while each thumb keeps its own aria-label:

<Field.Root>
  <Fieldset.Root>
    <Fieldset.Legend>Price range</Fieldset.Legend>
    <Slider.Root value={[25, 75]}>
      <Slider.Control>
        <Slider.Track>
          <Slider.Indicator />
          <Slider.Thumb index={0} aria-label="Minimum price" />
          <Slider.Thumb index={1} aria-label="Maximum price" />
        </Slider.Track>
      </Slider.Control>
    </Slider.Root>
  </Fieldset.Root>
</Field.Root>

API reference

Root

Groups all parts of the slider. Renders a <div> element with role="group".

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Label

An accessible label for the slider, associated with every thumb. Clicking it focuses the thumb of a single-thumb slider. Its ID defaults to one derived from the root's, and can be overridden with id. Renders a <div> element.

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Value

Displays the current value of the slider as text. Renders an <output> element pointing at the thumb inputs through for.

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Control

The interactive area of the slider: pressing anywhere inside it moves the nearest enabled thumb to that position and starts a drag. Renders a <div> element.

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Track

Contains the indicator and the thumbs, and represents the entire range of the slider. position: relative is set inline so the thumbs can be positioned against it. Renders a <div> element.

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Indicator

Visualizes the current value of the slider. Its offset and length along the track are set inline, from the values' percentage positions between min and max. Renders a <div> element.

PropTypeDefault
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Thumb

The draggable part of the slider at the tip of the indicator. Its offset along the track is set inline. Renders a <div> wrapping a visually hidden <input type="range">, which takes focus and carries the thumb's value and ARIA.

PropTypeDefault
AttributeDescription
data-indexThe thumb's index in the values array (0 on single-thumb sliders).
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 field is valid (when wrapped in Field.Root).
data-invalidPresent when the field is invalid (when wrapped in Field.Root).
data-touchedPresent when the field has been touched (when wrapped in Field.Root).
data-dirtyPresent when the value has changed (when wrapped in Field.Root).
data-focusedPresent when focused (when wrapped in Field.Root).

Keyboard:

KeyAction
ArrowRight / ArrowUpIncrement by step.
ArrowLeft / ArrowDownDecrement by step.
Shift + ArrowIncrement/decrement by largeStep.
PageUpIncrement by largeStep.
PageDownDecrement by largeStep.
HomeSet to min (or to the neighbouring thumb's bound on range sliders).
EndSet to max (or to the neighbouring thumb's bound on range sliders).

ArrowLeft and ArrowRight are swapped in right-to-left text direction.