Sliders

Sliders allow users to select a value or range from a continuous or discrete set of values by moving a handle along a track.


Basic Slider

A simple range input with Standard styling.

<div style="width: 100%; max-width: 320px;">
<input type="range" class="Slider" min="0" max="100" value="50">
</div>
<input type="range" class="Slider" min="0" max="100" value="50">

With Labels

Add min/max labels for context.

0 100
<div class="Slider-container" style="max-width: 320px;">
<div class="Slider-labels">
<span>0</span>
<span>100</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="50">
</div>
<div class="Slider-container">
    <div class="Slider-labels">
        <span>0</span>
        <span>100</span>
    </div>
    <input type="range" class="Slider" min="0" max="100" value="50">
</div>

With Value Display

Show the current value alongside the slider.

50%
<div class="Slider-container" style="max-width: 320px;">
<div class="Layout-split" style="margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Volume</label>
<span class="Slider-value">50%</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="50">
</div>
<div class="Slider-container">
    <div class="Layout-split">
        <label>Volume</label>
        <span class="Slider-value">50%</span>
    </div>
    <input type="range" class="Slider" min="0" max="100" value="50">
</div>
// Update value display
const slider = document.querySelector('.Slider');
const display = document.querySelector('.Slider-value');
slider.addEventListener('input', () => {
    display.textContent = slider.value + '%';
});

Discrete Steps

Use the step attribute for predefined increments.

Medium
Low Med High
<div class="Slider-container" style="max-width: 320px;">
<div class="Layout-split" style="margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Quality</label>
<span class="Slider-value">Medium</span>
</div>
<input type="range" class="Slider" min="1" max="5" step="1" value="3">
<div class="Slider-ticks">
<span>Low</span>
<span></span>
<span>Med</span>
<span></span>
<span>High</span>
</div>
</div>
<input type="range" class="Slider" min="1" max="5" step="1" value="3">
<div class="Slider-ticks">
    <span>Low</span>
    <span></span>
    <span>Med</span>
    <span></span>
    <span>High</span>
</div>

Sizes

Small
Default
Large
<div class="Layout-stack" style="max-width: 320px;">
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Small</small>
<input type="range" class="Slider Slider--small" min="0" max="100" value="30">
</div>
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Default</small>
<input type="range" class="Slider" min="0" max="100" value="50">
</div>
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Large</small>
<input type="range" class="Slider Slider--large" min="0" max="100" value="70">
</div>
</div>
<input type="range" class="Slider Slider--small" ...>
<input type="range" class="Slider" ...>
<input type="range" class="Slider Slider--large" ...>

Color Variants

Default (Accent)
Success
Warning
Error
<div class="Layout-stack" style="max-width: 320px;">
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Default (Accent)</small>
<input type="range" class="Slider" min="0" max="100" value="60">
</div>
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Success</small>
<input type="range" class="Slider Slider--success" min="0" max="100" value="80">
</div>
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Warning</small>
<input type="range" class="Slider Slider--warning" min="0" max="100" value="40">
</div>
<div>
<small style="color: var(--fg-3); display: block; margin-bottom: var(--space-2);">Error</small>
<input type="range" class="Slider Slider--error" min="0" max="100" value="20">
</div>
</div>
<input type="range" class="Slider Slider--success" ...>
<input type="range" class="Slider Slider--warning" ...>
<input type="range" class="Slider Slider--error" ...>

Disabled State

<div style="max-width: 320px;">
<input type="range" class="Slider" min="0" max="100" value="50" disabled>
</div>
<input type="range" class="Slider" min="0" max="100" value="50" disabled>

In Forms

Common slider use cases in forms.

75%
50%
60%
<div class="Layout-stack" style="max-width: 400px; padding: var(--space-4); background: var(--bg-s); border-radius: var(--r-m);">
<div class="Slider-container">
<div class="Layout-split" style="margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Brightness</label>
<span style="color: var(--fg-3);">75%</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="75">
</div>
<div class="Slider-container">
<div class="Layout-split" style="margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Contrast</label>
<span style="color: var(--fg-3);">50%</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="50">
</div>
<div class="Slider-container">
<div class="Layout-split" style="margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Saturation</label>
<span style="color: var(--fg-3);">60%</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="60">
</div>
</div>

Price Range Filter

Price Range
Min
$25
Max
$150
$0 $200
<div style="max-width: 320px; padding: var(--space-4); border: 1px solid var(--bd); border-radius: var(--r-m);">
<div style="font-weight: 600; margin-bottom: var(--space-3);">Price Range</div>
<div class="Layout-split" style="margin-bottom: var(--space-3);">
<div>
<small style="color: var(--fg-3);">Min</small>
<div style="font-size: 1.25rem; font-weight: 600;">$25</div>
</div>
<div style="text-align: right;">
<small style="color: var(--fg-3);">Max</small>
<div style="font-size: 1.25rem; font-weight: 600;">$150</div>
</div>
</div>
<input type="range" class="Slider" min="0" max="200" value="150">
<div class="Slider-labels" style="margin-top: var(--space-1);">
<span style="font-size: 0.75rem; color: var(--fg-3);">$0</span>
<span style="font-size: 0.75rem; color: var(--fg-3);">$200</span>
</div>
</div>

Common Patterns

Audio Player Volume

<div style="display: flex; align-items: center; gap: var(--space-3); max-width: 280px; padding: var(--space-3); background: var(--bg-s); border-radius: var(--r-m);">
<i class="ph ph-speaker-low" style="color: var(--fg-3);"></i>
<input type="range" class="Slider Slider--small" min="0" max="100" value="65">
<i class="ph ph-speaker-high" style="color: var(--fg-3);"></i>
</div>

Filter Panel Price Range

Price Range
$25 $150
$0 $200
<div style="max-width: 280px; padding: var(--space-4); border: 1px solid var(--bd); border-radius: var(--r-m);">
<div style="font-weight: 600; margin-bottom: var(--space-3);">Price Range</div>
<div style="display: flex; justify-content: space-between; margin-bottom: var(--space-2); font-size: 0.9rem;">
<span>$25</span>
<span>$150</span>
</div>
<input type="range" class="Slider" min="0" max="200" value="150">
<div class="Slider-labels" style="margin-top: var(--space-1);">
<span style="font-size: 0.75rem; color: var(--fg-3);">$0</span>
<span style="font-size: 0.75rem; color: var(--fg-3);">$200</span>
</div>
</div>

Settings Panel

75%
16px
<div style="max-width: 360px; padding: var(--space-4); background: var(--bg-s); border-radius: var(--r-m); display: flex; flex-direction: column; gap: var(--space-4);">
<div class="Slider-container">
<div style="display: flex; justify-content: space-between; margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Brightness</label>
<span style="color: var(--fg-3);">75%</span>
</div>
<input type="range" class="Slider" min="0" max="100" value="75">
</div>
<div class="Slider-container">
<div style="display: flex; justify-content: space-between; margin-bottom: var(--space-2);">
<label style="font-weight: 500;">Font Size</label>
<span style="color: var(--fg-3);">16px</span>
</div>
<input type="range" class="Slider" min="12" max="24" step="1" value="16">
</div>
</div>

Image Editor Toolbar

50
<div style="max-width: 300px; display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); border: 1px solid var(--bd); border-radius: var(--r-m);">
<i class="ph ph-sun-dim" style="color: var(--fg-3);"></i>
<input type="range" class="Slider Slider--small" min="0" max="100" value="50">
<i class="ph ph-sun" style="color: var(--fg-3);"></i>
<span style="font-size: 0.8rem; font-weight: 600; min-width: 3ch; text-align: right;">50</span>
</div>

Customization

Override slider styling with CSS custom properties:

/* Custom thumb color */
.Slider--brand::-webkit-slider-thumb {
  background: oklch(55% 0.25 280);
}

.Slider--brand::-moz-range-thumb {
  background: oklch(55% 0.25 280);
}

Filled Track

/* Show filled portion of track (requires JS to update) */
.Slider--filled {
  background: linear-gradient(
    to right,
    var(--accent) 0%,
    var(--accent) var(--fill, 50%),
    var(--bd) var(--fill, 50%),
    var(--bd) 100%
  );
}

Larger Touch Target

.Slider--touch::-webkit-slider-thumb {
  width: 28px;
  height: 28px;
  margin-top: -11px;
}

Theming

[data-theme="dark"] .Slider {
  background: oklch(30% 0 0);
}

[data-theme="dark"] .Slider::-webkit-slider-thumb {
  background: oklch(70% 0.15 260);
  box-shadow: 0 2px 4px oklch(0% 0 0 / 0.4);
}

API Reference

ClassDescription
.SliderBase range input styling
.Slider--smallSmaller track and thumb
.Slider--largeLarger track and thumb
.Slider--successGreen thumb color
.Slider--warningYellow thumb color
.Slider--errorRed thumb color
.Slider-containerWrapper for slider + labels
.Slider-labelsMin/max label container
.Slider-ticksDiscrete step labels
.Slider-valueCurrent value display

HTML Attributes

AttributeDescription
minMinimum value
maxMaximum value
valueCurrent value
stepIncrement amount (for discrete steps)
disabledNon-interactive state

ARIA Attributes

AttributeDescription
aria-valueminMinimum value (auto from min)
aria-valuemaxMaximum value (auto from max)
aria-valuenowCurrent value (auto from value)
aria-valuetextHuman-readable value (e.g., "50 percent")

CSS Reference

/* Base slider */
.Slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  background: var(--bd);
  border-radius: 3px;
  outline: none;
  cursor: pointer;
}

/* Track - WebKit */
.Slider::-webkit-slider-runnable-track {
  height: 6px;
  background: var(--bd);
  border-radius: 3px;
}

/* Thumb - WebKit */
.Slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  background: var(--accent);
  border-radius: 50%;
  cursor: pointer;
  margin-top: -6px;
  box-shadow: 0 2px 4px oklch(0% 0 0 / 0.2);
  transition: transform 0.15s, box-shadow 0.15s;
}

.Slider::-webkit-slider-thumb:hover {
  transform: scale(1.1);
}

.Slider::-webkit-slider-thumb:active {
  transform: scale(0.95);
  box-shadow: 0 1px 2px oklch(0% 0 0 / 0.2);
}

/* Thumb - Firefox */
.Slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  background: var(--accent);
  border: none;
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2px 4px oklch(0% 0 0 / 0.2);
}

/* Focus */
.Slider:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Disabled */
.Slider:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Sizes */
.Slider--small { height: 4px; }
.Slider--small::-webkit-slider-thumb { width: 14px; height: 14px; margin-top: -5px; }
.Slider--large { height: 8px; }
.Slider--large::-webkit-slider-thumb { width: 22px; height: 22px; margin-top: -7px; }

/* Color variants */
.Slider--success::-webkit-slider-thumb { background: oklch(55% 0.15 150); }
.Slider--warning::-webkit-slider-thumb { background: oklch(70% 0.15 80); }
.Slider--error::-webkit-slider-thumb { background: oklch(55% 0.2 25); }

/* Container helpers */
.Slider-container {
  width: 100%;
}

.Slider-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.8rem;
  color: var(--fg-3);
  margin-bottom: var(--space-1);
}

.Slider-ticks {
  display: flex;
  justify-content: space-between;
  font-size: 0.75rem;
  color: var(--fg-3);
  margin-top: var(--space-1);
}

.Slider-value {
  font-weight: 600;
  color: var(--fg);
  min-width: 3ch;
  text-align: right;
}

Accessibility

Keyboard Support

Key Action
Arrow Left / Arrow Down Decrease value by one step
Arrow Right / Arrow Up Increase value by one step
Home Set to minimum value
End Set to maximum value
Page Up / Page Down Increase/decrease by larger step

Screen Reader Guidance

Native <input type="range"> is accessible by default. Enhance with aria-valuetext for human-readable values (e.g., “50 percent” instead of “50”). Always associate a <label> with the slider.

<label for="volume-slider">Volume</label>
<input
    type="range"
    id="volume-slider"
    class="Slider"
    min="0"
    max="100"
    value="50"
    aria-valuetext="50 percent"
>

ARIA Attributes

  • aria-valuemin, aria-valuemax, aria-valuenow are automatic for native range inputs
  • aria-valuetext for human-readable value descriptions
  • Always use <label> to describe the slider’s purpose
  • Ensure visible focus indicators on the thumb

Best Practices

Do

  • Show the current value — Display the number alongside the slider for clarity
  • Use appropriate ranges — Match real-world constraints (0–100 for percentages)
  • Add labels — Min/max labels help users understand the scale
  • Consider discrete steps — Snapping to values is often easier than continuous
  • Associate a label element — Screen readers need to know what the slider controls
  • Provide adequate thumb size — At least 44px touch target on mobile

Don’t

  • Use for precise numeric input — A text field is better when exact values matter
  • Hide the scale — Users need to understand the range boundaries
  • Make the thumb too small — Tiny thumbs are frustrating on touch devices
  • Forget keyboard users — Native range inputs handle this; custom sliders must too
  • Use sliders for binary choices — A toggle switch is more appropriate
  • Skip the value display — Sliders without visible values feel imprecise