Skip to content
Ndop UI

Slider

Picks a number, or a range with two thumbs, by dragging along a track or with the arrow keys.

import { Component } from '@angular/core';
import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';

/** A single value from 0 to 100. */
@Component({
  selector: 'example-slider-demo',
  imports: [Slider, SliderRange, SliderThumb, SliderTrack],
  template: `
    <div ndSlider class="w-3/5">
      <div ndSliderTrack><div ndSliderRange></div></div>
      <span ndSliderThumb aria-label="Volume" [defaultValue]="50"></span>
    </div>
  `,
})
export class SliderDemoExample {}

A slider picks a number from a range by dragging a thumb along a track, or a range with two thumbs, such as a price filter. Each thumb is a slider with its own value, which the arrow keys change by a step and Page Up and Page Down by ten; range thumbs can't pass each other. Horizontal sliders run from the start side, so they mirror in right-to-left layouts, and vertical ones run bottom to top.

Installation

npx @ndop/cli add slider

Usage

import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';
<div ndSlider class="w-3/5">
  <div ndSliderTrack><div ndSliderRange></div></div>
  <span ndSliderThumb aria-label="Volume" [defaultValue]="50"></span>
</div>

Examples

Slider range

A price range with two thumbs, and the values shown beside it.

Price $100 – $350
import { Component, signal } from '@angular/core';
import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';

/**
 * A price filter with two thumbs that can't pass each other, and the
 * chosen range shown beside it.
 */
@Component({
  selector: 'example-slider-range',
  imports: [Slider, SliderRange, SliderThumb, SliderTrack],
  template: `
    <div class="grid w-full max-w-sm gap-3">
      <div class="flex items-center justify-between gap-2">
        <span id="slider-range-label" class="text-sm font-medium">Price</span>
        <span class="text-sm text-muted-foreground">
          {{ '$' + min() }} – {{ '$' + max() }}
        </span>
      </div>
      <div ndSlider [max]="500" [step]="10">
        <div ndSliderTrack><div ndSliderRange></div></div>
        <span
          ndSliderThumb
          aria-label="Minimum price"
          [valueText]="'$' + min()"
          [(value)]="min"
        ></span>
        <span
          ndSliderThumb
          aria-label="Maximum price"
          [valueText]="'$' + max()"
          [(value)]="max"
        ></span>
      </div>
    </div>
  `,
})
export class SliderRangeExample {
  protected readonly min = signal(100);
  protected readonly max = signal(350);
}

Slider steps

Steps of ten, with the value said in words for screen readers.

Quality: 60%, balanced

import { Component, computed, signal } from '@angular/core';
import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';

/** Steps of ten, with the value said in words for screen readers. */
@Component({
  selector: 'example-slider-steps',
  imports: [Slider, SliderRange, SliderThumb, SliderTrack],
  template: `
    <div class="grid w-full max-w-sm gap-3">
      <div ndSlider [step]="10">
        <div ndSliderTrack><div ndSliderRange></div></div>
        <span
          ndSliderThumb
          aria-label="Quality"
          [valueText]="label()"
          [(value)]="quality"
        ></span>
      </div>
      <p class="text-sm text-muted-foreground">Quality: {{ label() }}</p>
    </div>
  `,
})
export class SliderStepsExample {
  protected readonly quality = signal(60);

  protected readonly label = computed(() => {
    const quality = this.quality();
    const trade =
      quality < 40
        ? 'smaller files'
        : quality < 80
          ? 'balanced'
          : 'best quality';
    return `${quality}%, ${trade}`;
  });
}

Slider vertical

A vertical slider, running bottom to top.

import { Component } from '@angular/core';
import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';

/** A vertical slider, running bottom to top. */
@Component({
  selector: 'example-slider-vertical',
  imports: [Slider, SliderRange, SliderThumb, SliderTrack],
  template: `
    <div class="flex h-44 items-center gap-6">
      <div ndSlider orientation="vertical">
        <div ndSliderTrack><div ndSliderRange></div></div>
        <span ndSliderThumb aria-label="Bass" [defaultValue]="30"></span>
      </div>
      <div ndSlider orientation="vertical">
        <div ndSliderTrack><div ndSliderRange></div></div>
        <span ndSliderThumb aria-label="Treble" [defaultValue]="70"></span>
      </div>
    </div>
  `,
})
export class SliderVerticalExample {}

Slider disabled

A disabled slider.

import { Component } from '@angular/core';
import {
  Slider,
  SliderRange,
  SliderThumb,
  SliderTrack,
} from '@/ui/slider/slider';

/** A disabled slider. */
@Component({
  selector: 'example-slider-disabled',
  imports: [Slider, SliderRange, SliderThumb, SliderTrack],
  template: `
    <div ndSlider disabled class="w-3/5">
      <div ndSliderTrack><div ndSliderRange></div></div>
      <span ndSliderThumb aria-label="Brightness" [defaultValue]="40"></span>
    </div>
  `,
})
export class SliderDisabledExample {}

Accessibility

Implements the Slider pattern of the WAI-ARIA Authoring Practices.

Keyboard

Keys the component handles.

Key Action
ArrowRightorArrowUpIncreases the focused thumb's value by one step.
ArrowLeftorArrowDownDecreases the focused thumb's value by one step.
PageUpIncreases the value by ten steps.
PageDownDecreases the value by ten steps.
HomeSets the value to its minimum: the slider's, or the previous thumb's.
EndSets the value to its maximum: the slider's, or the next thumb's.

Focus

  • Keys and dragging keep focus on the thumb; pressing the slider's track focuses the thumb it moves.
  • Leaving a thumb marks its bound field touched.

Right-to-left

Horizontal sliders run from the start side, so the minimum is on the right in right-to-left layouts, and ArrowLeft increases the value while ArrowRight decreases it. Vertical sliders run bottom to top in either direction. Direction comes from the nearest dir attribute, on the slider or an ancestor.

API reference

Slider

[ndSlider]

A slider (@ndop/primitives/slider): pick a number, or a range with two thumbs, by dragging or with the arrow keys. The root sets min, max and step; each ndSliderThumb holds one value, so bind each thumb with [(value)] or a forms library, and name it with aria-label.

Slider binding Type Default
[class]
Extra classes, merged last so they win.
string''
[min]
The lowest value.
number0
[max]
The highest value.
number100
[step]
The distance between allowed values, from min.
number1
[orientation]
Horizontal (from the start side) or vertical (from the bottom).
'horizontal' | 'vertical''horizontal'
[disabled]
Disables every thumb.
booleanfalse
Data attribute Values
data-slotslider
data-orientation —
data-disabled —

SliderTrack

[ndSliderTrack]

The track of a Slider, with a ndSliderRange inside.

SliderTrack binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotslider-track
data-orientation —
data-disabled —

SliderRange

[ndSliderRange]

The filled part of a Slider's track: from the start to a single thumb, or between two.

SliderRange binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotslider-range
data-orientation —

SliderThumb

[ndSliderThumb]role slider

A thumb of a Slider: one value, bound with [(value)] or a forms library. Name it with aria-label; add a second one for a range. It stays inside the track at both ends.

Manages aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext, aria-orientation, aria-disabled, aria-invalid.

SliderThumb binding Type Default
[class]
Extra classes, merged last so they win.
string''
[defaultValue]
Initial value when it isn't bound.
number | undefinedundefined
[valueText]
Text for aria-valuetext, when the number alone doesn't say it.
string | undefined—
[invalid]
Marks the thumb invalid when no forms library is bound; with one, the field's state decides.
booleanfalse
[(value)]
This thumb's value. Two-way bindable with [(value)].
number0
Data attribute Values
data-slotslider-thumb
data-orientation —
data-disabled —
data-invalid —

Agent notes

Do

  • Name every thumb, such as "Minimum price" and "Maximum price".
  • Show the value next to the slider, and set valueText when a number alone doesn't say it, such as "$40".

Don't

  • Don't use a slider when an exact value matters; use an Input of type number.
  • Don't use a slider for a few distinct options; use a Radio Group.