Skip to content

Slider

<div class="tng-slider">
<div class="tng-slider-track"></div>
</div>
<div class="tng-slider">
<div class="tng-slider-labels">
<div style="--tng-slider-label-value: 0.5">50%</div>
</div>
<div class="tng-slider-bar">
<div class="tng-slider-track"></div>
<div
class="tng-slider-thumb"
style="--tng-slider-thumb-value: 0.5"
></div>
</div>
<div class="tng-slider-labels">
<div style="--tng-slider-label-value: 0">0%</div>
<div style="--tng-slider-label-value: 0.5">50%</div>
<div style="--tng-slider-label-value: 1">100%</div>
</div>
</div>
50%
0%
50%
100%
<div class="tng-slider">
<div class="tng-slider-bar">
<div class="tng-slider-track"></div>
<div
class="tng-slider-progress"
style="
--tng-slider-progress-start: 0.5;
--tng-slider-progress-end: 0.75;
"
></div>
</div>
</div>
<div class="tng-slider">
<div class="tng-slider-bar">
<div class="tng-slider-track"></div>
<div class="tng-slider-thumb"></div>
<div
class="tng-slider-thumb"
style="--tng-slider-thumb-value: 0.25"
></div>
<div
class="tng-slider-thumb"
style="--tng-slider-thumb-value: 0.5"
></div>
</div>
</div>
<div class="tng-slider">
<div class="tng-slider-bar">
<div class="tng-slider-track"></div>
<div
class="tng-slider-thumb is-active"
style="--tng-slider-thumb-value: 0.5"
></div>
</div>
</div>
<div class="tng-slider">
<div class="tng-slider-bar">
<div class="tng-slider-track"></div>
<div
class="tng-slider-thumb is-disabled"
style="--tng-slider-thumb-value: 0.5"
></div>
</div>
</div>
<div class="tng-slider">
<div class="tng-slider-track"></div>
</div>
<div class="tng-slider is-ev">
<div class="tng-slider-track"></div>
</div>
<div class="tng-slider is-range">
<div class="tng-slider-track"></div>
</div>
<div class="tng-slider is-temperature">
<div class="tng-slider-track"></div>
</div>
<div class="tng-slider">
<input style="--tng-slider-progress-value: 0.5" type="range" />
</div>

A <datalist> linked via list / id provides snap-to-step behavior on the native range input. Hide it with .sr-only since rendering is inconsistent across browsers, and use a separate .tng-slider-labels div with role="presentation" for the visual labels.

<div class="tng-slider">
<input
list="slider-options"
type="range"
min="0"
max="100"
step="25"
/>
<datalist class="sr-only" id="slider-options">
<option value="0"></option>
<option value="25"></option>
<option value="50"></option>
<option value="75"></option>
<option value="100"></option>
</datalist>
<div class="tng-slider-labels" role="presentation">
<div style="--tng-slider-label-value: 0">0%</div>
<div style="--tng-slider-label-value: 0.25">25%</div>
<div style="--tng-slider-label-value: 0.5">50%</div>
<div style="--tng-slider-label-value: 0.75">75%</div>
<div style="--tng-slider-label-value: 1">100%</div>
</div>
</div>
<div class="tng-slider">
<input disabled type="range" />
</div>

Use <input type="range"> whenever possible — it provides role="slider" implicitly, with built-in aria-valuemin, aria-valuemax, and aria-valuenow derived from the min, max, and value attributes.

When using the custom div-based elements (.tng-slider-thumb, .tng-slider-track) for visual purposes, an underlying <input type="range"> should still drive the semantics. If a native input cannot be used, apply role="slider" manually together with the required ARIA properties.

Associate a visible label with the input via <label for="..."> or aria-label / aria-labelledby. The top value labels (.tng-slider-labels) are visual — they do not provide an accessible name by themselves.

Use aria-valuetext when the numeric value alone is not meaningful (e.g. aria-valuetext="50%" or aria-valuetext="Moderate").

KeyAction
/ Decrease / increase value by one step
/ Decrease / increase value by one step
Home / EndSet to minimum / maximum value
Page Up / Page DownIncrease / decrease by a larger step (browser-defined)

Native <input type="range"> handles all of the above automatically.

Using the native disabled attribute on <input type="range"> removes it from the tab order and announces it as disabled to assistive technologies.

The .is-disabled class on .tng-slider-thumb is visual only — pair it with aria-disabled="true" if used on a custom role="slider" element.

Use .sr-only on <datalist> to visually hide it — browser rendering of datalist for range inputs is inconsistent (Safari renders nothing, Firefox ignores positioning). The list / id linkage still provides snap-to-step behavior where supported.

The visual .tng-slider-labels div should use role="presentation" since the labels are decorative duplicates of the datalist values and should not be announced separately.