---
url: https://nsh.dev/ru/elements/slider
description: "Компонент Slider: значение или диапазон из двух значений, выбираемые на треке, с поддержкой нажатий и перетаскивания на сенсорных экранах и с помощью мыши, клавиш слайдера из WAI-ARIA и значения для программ чтения с экрана на каждом ползунке, а также с треком, индикатором, диапазоном и ползунками, стиль которых задаёт приложение."
demo: https://nsh.dev/play/index.html#el-slider
---

# Slider

Слайдер выбирает значение на дорожке (громкость, яркость) или диапазон из двух значений, у каждого из которых свой бегунок (ценовой диапазон). Поведение он берёт на себя: нажатие на дорожку переносит туда ближайший бегунок, бегунок следует за перетаскиванием мышью или пальцем; значения привязываются к `step`; каждый бегунок является остановкой клавиатурного фокуса и перемещается стрелками, а для скринридеров это регулируемый элемент со своим значением. Внешний вид задаёт приложение: у `Slider` нет собственных цветов, размеров, радиусов и анимации. Он состоит из дорожки с индикатором внутри, диапазона и бегунков на индикаторе, а затем его дочерних элементов (подпись, значение в виде текста), и приложение стилизует каждую часть.

```rust
use nsh_base::{SliderValue, slider, theme};
use nsh_core::prelude::*;

fn strength(level: Signal<SliderValue>) -> impl IntoElement {
    let percent = move || format!("{}%", level.get().end());
    slider()
        .value(move || level.get())
        .accessibility_label("Option strength")
        .on_change(move |next: &SliderValue| level.set(*next))
        // The value in a column after the rail.
        .flex_row()
        .items_center()
        .gap_2()
        // An 8-point rail; the thumbs travel inside it.
        .track_style(|s| s.h(px(8.0)).flex_1().px(px(8.0)).rounded_full().bg(move || theme().muted))
        .indicator_style(|s| s.h_full())
        // The fill, from the rail's edge, fading into the thumb's tone.
        .range_style(|s| {
            s.ml(-px(8.0))
                .rounded_full()
                .bg_linear_to_r()
                .gradient_from(move || theme().primary)
                .gradient_to(move || theme().muted_foreground)
        })
        // A 16 x 8 pill sitting in the rail.
        .thumb_style(|s| {
            s.w(px(16.0))
                .h(px(8.0))
                .rounded_full()
                .bg(move || theme().muted_foreground)
                .hover(|s| s.brightness(1.1))
                .active(|s| s.brightness(1.25))
        })
        .child(
            text(percent)
                .w(px(42.0))
                .text_right()
                .text_xs()
                .tabular_nums()
                .text_color(move || theme().muted_foreground),
        )
}
```

```tsx
import { mount, signal, Text } from "nsh";
import { Slider } from "nsh/base";

function App() {
  const strength = signal(64);
  return (
    <Slider
      value={strength()}
      onChange={({ value }) => strength.set(value as number)}
      accessibilityLabel="Option strength"
      class="flex-row items-center gap-2"
      trackClass="h-2 flex-1 px-2 rounded-full bg-muted focus-within:ring-2 focus-within:ring-primary/36 focus-within:ring-offset-2 focus-within:ring-offset-background disabled:opacity-35"
      indicatorClass="h-full"
      rangeClass="-ml-2 rounded-full bg-linear-to-r from-primary via-primary to-muted-foreground"
      thumbClass="h-2 w-4 rounded-full bg-muted-foreground transition hover:brightness-110 active:brightness-125 focus:outline-none disabled:brightness-100 motion-reduce:transition-none"
      thumbStyle={{ boxShadow: "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)" }}
    >
      <Text class="w-[42px] text-right font-mono text-[11px] text-muted-foreground">{`${strength()}%`}</Text>
    </Slider>
  );
}

mount(App);
```

`Slider` поставляется из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), крейта `nsh-base` для Rust. В Rust `slider()` создаёт его с сеттером для каждого свойства (`.value(..)`, `.thumb_style(..)`), методом-слушателем для каждого события (`.on_change(..)`), типизированными методами стиля для самого слайдера и `.child(..)` для его подписи. Его значение — `SliderValue`: `Single(f64)` или `Range(f64, f64)`.

<a id="value"></a>

## Значение

Значением служит число или `[start, end]` для диапазона с двумя бегунками; форма значения определяет, сколько будет бегунков. Приложение владеет значением через `value` и устанавливает то, о котором сообщает `onChange`, как и для всех элементов управления `nsh/base`; либо оставляет значение слайдеру через `defaultValue` и всё равно получает уведомления об изменениях. Во время перетаскивания бегунки и диапазон следуют за указателем в том же кадре, нативно; когда указатель отпускается, снова показывается `value` приложения, то есть то, которое приложение задало для последнего изменения при перетаскивании. Скринридеры озвучивают значения перетаскивания по ходу, пока оно идёт, а после него — только конечное значение.

```tsx
const price = signal<[number, number]>([200, 800]);

<Slider
  value={price()}
  onChange={({ value }) => price.set(value as [number, number])}
  onChangeEnd={({ value }) => save(value)}
  min={0}
  max={1000}
  step={10}
  accessibilityLabel="Maximum price"
  startAccessibilityLabel="Minimum price"
/>;
```

- `onChange` сообщает о каждом новом значении, которое запрашивает пользователь: при нажатии на дорожку, на каждом шаге перетаскивания, при каждой клавише. Каждое сообщение — это новое значение.
- `onChangeEnd` один раз сообщает значение, на котором закончилось изменение: когда указатель отпускается после нажатия или перетаскивания, задавших значение, и после каждого шага клавишей или скринридером (каждый из них — законченное изменение). Сохраняйте или отправляйте значение здесь.
- Значения привязываются к `step`, считая от `min` (`min={1} step={2}` даёт 1, 3, 5, ...), и остаются между `min` и `max`; `max` всегда достижим, поэтому последний шаг короче, если диапазон не состоит из целого числа шагов. `step={0}` принимает любое значение.
- Бегунки диапазона никогда не пересекаются: перетаскиваемый бегунок останавливается у другого. Если перетаскивание начинается на двух бегунках с одним значением, двигается тот, который может пойти в сторону указателя.
- Показываемое значение — значение приложения, ограниченное `min` и `max` (концы диапазона по порядку), а значение вне сетки шага показывается там, где оно есть; следующее изменение пользователя привяжется к сетке.

<a id="props"></a>

## Свойства

| Свойство | Rust | Тип | По умолчанию | Описание |
|---|---|---|---|---|
| `value` | `.value(..)` | `number \| [number, number]` | нет | Значение (управляемое): число или диапазон с двумя бегунками. Приложение устанавливает то значение, о котором сообщает `onChange`. |
| `defaultValue` | `.default_value(..)` | `number \| [number, number]` | `0` | Начальное значение, пока `value` не задано; тогда слайдер хранит своё значение сам. |
| `min` | `.min(..)` | `number` | `0` | Наименьшее значение. |
| `max` | `.max(..)` | `number` | `100` | Наибольшее значение; значение меньше `min` приравнивается к `min`. |
| `step` | `.step(..)` | `number` | `1` | Шаг, к которому привязываются значения, считая от `min`; `0` принимает любое значение. |
| `scale` | `.scale(..)` | `"linear" \| "logarithmic"` | `"linear"` | Как значения распределяются по дорожке: равномерно или по порядкам величины (от 1 до 1000 треть пути соответствует 10). Логарифмической шкале нужен `min` больше 0; при меньшем `min` шкала линейная. |
| `axis` | `.axis(..)` | `"horizontal" \| "vertical"` | `"horizontal"` | Направление дорожки: `min` в начале (слева, а в раскладке справа налево — справа) или внизу. Считывается при создании слайдера. |
| `disabled` | `.disabled(..)` | `boolean` | `false` | Никаких изменений указателем, клавиатурой или скринридером; классы `disabled:` применяются к слайдеру и его частям. |
| `accessibilityLabel` | `.accessibility_label(..)` | `string` | нет | Что скринридеры произносят для бегунка (в диапазоне — для конечного). |
| `startAccessibilityLabel` | `.start_accessibility_label(..)` | `string` | `accessibilityLabel` | Что скринридеры произносят для начального бегунка диапазона. |
| `trackClass`, `trackStyle` | `.track_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Дорожка: область, принимающая нажатия и перетаскивания. |
| `indicatorClass`, `indicatorStyle` | `.indicator_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Индикатор, направляющая, на которую отображаются значения. |
| `rangeClass`, `rangeStyle` | `.range_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Диапазон: от `min` до бегунка (между бегунками диапазона), размещается слайдером. |
| `thumbClass`, `thumbStyle` | `.thumb_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Каждый бегунок, центрированный на своём значении. |
| `children` | `.child(..)` | `JSX.Element` | нет | Содержимое после дорожки: подпись, значение в виде текста. |
| `class`, `style` | методы стиля | `string`, `StyleProp<ViewStyle>` | нет | Сам слайдер: колонка (или строка) вокруг дорожки и подписи. |

<a id="events"></a>

## События

| Событие | Rust | Данные | Когда |
|---|---|---|---|
| `onChange` | `.on_change(\|value: &SliderValue\| ..)` | `{ value: number \| [number, number] }` | Пользователь запросил новое значение: нажатие на дорожку, шаг перетаскивания, клавиша, увеличение или уменьшение скринридером. |
| `onChangeEnd` | `.on_change_end(\|value: &SliderValue\| ..)` | `{ value: number \| [number, number] }` | Изменение закончилось: указатель отпущен, шаг клавишей или скринридером выполнен. Один раз на изменение. |

<a id="parts"></a>

## Части

| Часть | Внутри | Стилизуется через |
|---|---|---|
| Слайдер | | `class`, `style` |
| Дорожка | слайдер, первой | `trackClass`, `trackStyle` |
| Индикатор | дорожка | `indicatorClass`, `indicatorStyle` |
| Диапазон | индикатор, размещается по значениям | `rangeClass`, `rangeStyle` |
| Бегунок (два в диапазоне) | индикатор, центрируется на своём значении | `thumbClass`, `thumbStyle` |
| Подпись | слайдер, после дорожки | собственные элементы (`children`) |

Дорожка принимает нажатия и перетаскивания. Длина индикатора соответствует диапазону значений, от `min` на его передней кромке (у вертикального слайдера — снизу); слайдер размещает на нём диапазон и бегунки, причём бегунок центрируется на своём значении независимо от размера. Есть два способа раскладки:

- Дорожка сама является направляющей (пример выше): `h-2 px-2 rounded-full bg-muted` с индикатором `h-full`. Отступ удерживает бегунки внутри направляющей на обоих концах, а `-ml-2` диапазона начинает заливку от самого края направляющей.
- Дорожка — полоса вокруг направляющей, выше неё, чтобы палец находил её на сенсорном экране: `h-8 justify-center` с индикатором `h-2 rounded-full` (`w-8 items-center` и `w-2 h-full` у вертикального слайдера).

Состояния, которые показывают части:

- Бегунок: `hover:` под указателем, `active:` при перетаскивании, `focus:` при фокусе клавиатуры, `disabled:`.
- Дорожка: `active:`, пока нажатие на ней перетаскивает; это `group`, поэтому индикатор, диапазон и бегунки берут у неё `group-hover:` и `group-active:`.
- Каждая часть: `disabled:`, пока таким является слайдер.

Бегунки движутся при изменении значения и сразу следуют за перетаскиванием. Анимация состояний бегунков — выбор приложения (`transition` для бегунка, который становится ярче при `hover:` и `active:`), а `motion-reduce:` оставляет его неподвижным для тех, кто просит меньше анимации. Фокус клавиатуры принадлежит бегунку: `focus-within:` на дорожке обводит всю направляющую, пока фокус у бегунка, а `focus:outline-none` бегунка занимает место собственной рамки фокуса платформы.

<a id="keyboard"></a>

## Клавиатура

На macOS и web каждый бегунок является остановкой клавиатурного фокуса (Tab), а бегунок в фокусе принимает клавиши слайдера из WAI-ARIA:

| Клавиша | Действие |
|---|---|
| Right, Up | Один шаг вверх (также у вертикального слайдера). |
| Left, Down | Один шаг вниз. |
| Page Up, Page Down | Десятая часть диапазона вверх или вниз (по шкале), но не меньше одного шага. |
| Home, End | Максимально вниз или вверх, насколько проходит бегунок (начало диапазона — вплоть до его конца). |

В раскладке справа налево горизонтальный слайдер идёт справа, его `min` находится на правом конце: Left — шаг вверх, а Right — шаг вниз, и перетаскивание влево увеличивает значение. У вертикального слайдера Right и Up по-прежнему означают шаг вверх.

Каждая клавиша, меняющая значение, вызывает `onChange`, а затем `onChangeEnd`. Стрелки и Page Up/Page Down повторяются, пока их удерживают. Клавиши доходят до слайдера, только пока не редактируется текстовое поле.

<a id="accessibility"></a>

## Доступность

- Каждый бегунок — регулируемый элемент (`"adjustable"`, в браузере slider) со своей подписью, своим значением и значениями, которые он может принимать: начало диапазона от `min` до конца, конец от начала до `max`, так что скринридеры читают и задают оба. Значение произносится как число с десятичными знаками шага (`2.5`).
- Действия скринридера «увеличить» и «уменьшить» (жесты VoiceOver — смахивание вверх и вниз) двигают бегунок на шаг так же, как стрелки.
- Бегунки — элементы слайдера для скринридеров, их имя задаёт `accessibilityLabel` (а для начала диапазона — `startAccessibilityLabel`); видимая подпись среди дочерних элементов читается там, где она находится.
- Пока `disabled`, скринридеры сообщают, что бегунки неактивны, и действий они не принимают.

Общие сведения о подписях, ролях и действиях см. в разделе [Доступность](https://nsh.dev/translations/ru/docs/accessibility.md).

<a id="platform-notes"></a>

## Примечания по платформам

- Каждая платформа рисует то, что стилизует приложение. Нажатия и перетаскивания отслеживаются в ядре для мыши, пера или пальца, а прокручиваемая область вокруг слайдера не перехватывает перетаскивание, начатое на нём.
- Tab перемещает клавиатурный фокус между бегунками на macOS и web, где бегунок в фокусе принимает клавиши слайдера.
- Скринридеры: VoiceOver (iOS, macOS) и TalkBack регулируют каждый бегунок; UI Automation (Windows), GTK accessibility (Linux) и браузер (`role="slider"`, `aria-valuenow`, `aria-valuetext`) получают его диапазон и значение.
