Sliders
Sliders allow users to select a value or a range of values by sliding a "handle" along a track. The MD3 Expressive Slider features a signature pill-shaped handle, gap spacing, asymmetric corner radii, and interactive compression effects.
Sliders are ideal controls when users need to select from a continuous or discrete range of values. The MD3 Expressive edition stands out with its 4px pill handle that compresses to 2px when pressed, a 6px gap between the track and handle, and asymmetric corner radii (outer=full/custom, inner=2px).
Introduction
The MD3 Expressive Slider is a high-fidelity input component designed for precision and tactile feedback. It features a unique "pill-shaped" thumb that responds to touch by compressing, mimicking the physical sensation of pressing a button. It supports various configurations, including centered tracks for neutral-point adjustments, range sliders for selecting intervals, discrete snapping steps, and track inset icons with automatic state transitions.
Anatomy
- Track: The horizontal (or vertical) bar representing the range of values.
- Thumb (Handle): The interactive element the user slides. Features a 4dp pill shape that compresses to 2dp on press.
- Active Track: The highlighted portion of the track indicating the selected value. Uses
var(--md-sys-color-[variant]). - Inactive Track: The remaining portion of the track. Uses
var(--md-sys-color-[variant]-container). - Value Indicator (Optional): A tooltip that displays the current numerical value during interaction.
- Ticks (Optional): Visual dot marks indicating discrete steps on the track. Ticks on the active track adapt to
var(--md-sys-color-on-[variant])for optimal visual contrast.
Variants
Basic
The standard interactive slider component with value indicator tooltip.
Continuous vs Discrete
- Continuous: Allows selection of any value within the range.
- Discrete: Snaps to specific intervals defined by the
stepprop. SetshowTickstotrueto visualize step marks along the track.
Track Sizes
Supports 5 Material 3 track sizes ranging from xs (4dp) to xl (32dp).
Color Variants
Supports 4 standard Material 3 color schemes: primary, secondary, tertiary, and error.
Centered Track
Ideal for settings that have a neutral starting point (e.g., exposure, pan, or balance), where the active track grows from the center (50%) outwards.
Range Slider
Allows users to select a range of values using two independent thumbs. Prevents thumbs from crossing each other and automatically handles z-index ordering for the last-dragged thumb.
Features
Inset Icons
For track sizes of md (40dp) or larger, you can place icons directly inside the track. Inset icons dynamically transition between the active and inactive track segments depending on thumb position.
insetIcon: Leading icon (rendered inside track).insetIconAtMin: Alternate icon displayed whenvalue === min(e.g., swapping a speaker icon for a mute icon at 0%).insetIconTrailing: Trailing icon rendered at the far end.insetIconAtMax: Alternate icon displayed whenvalue === max.
External Icons
In addition to track inset icons, leading and trailing icons can be placed alongside the slider using standard container flex layouts.
Custom Track Shapes
The trackShape prop configures outer track corner rounding:
"md3"(default): MD3 specific border radius per size."full": Fully rounded pill shape (size / 2).number: Custom pixel radius (e.g.,4for subtle rounded corners).
Disabled State
When disabled is set to true, interaction is disabled and standard 38% opacity is applied.
Thumb Compression
On press or touch, the thumb width reduces from 4dp to 2dp, providing a subtle but powerful tactile feedback loop driven by Motion spring animations.
Orientation
Supports both horizontal and vertical layouts. Vertical sliders are useful for volume or intensity controls in dense interfaces.
Usage
Basic Usage
import { Slider } from "@bug-on/m3-expressive";
import { useState } from "react";
const [value, setValue] = useState(50);
<Slider
value={value}
onValueChange={setValue}
min={0}
max={100}
/>
Range Selection
import { RangeSlider } from "@bug-on/m3-expressive";
import { useState } from "react";
const [range, setRange] = useState([20, 80]);
<RangeSlider
value={range}
onValueChange={setRange}
min={0}
max={100}
/>
Inset Icon with Swapping at Min
import { Slider } from "@bug-on/m3-expressive";
import { Volume2, VolumeX } from "lucide-react";
import { useState } from "react";
const [volume, setVolume] = useState(0);
<Slider
value={volume}
onValueChange={setVolume}
size="lg"
insetIcon={<Volume2 className="size-5" />}
insetIconAtMin={<VolumeX className="size-5" />}
/>
Best Practices
Do
- Use sliders for settings where the relative value change matters more than exact digits (e.g., volume, brightness, zoom).
- Provide immediate feedback as the user slides the thumb (
onValueChange). - Use
onValueChangeEndwhen triggering expensive operations (like API queries) on release. - Use
isCenteredfor adjustments with a logical zero or middle point. - Ensure the slider has an
aria-labelor is linked viaaria-labelledby.
Don't
- Don't use a slider for entering precise data where a Text Field would be more efficient.
- Don't crowd too many sliders into a single view.
- Avoid using Inset Icons for small track sizes (
xs,sm) as they are disabled by spec to prevent clutter.
Accessibility
- Keyboard: Navigate with Arrow keys (fine-tuning) and
Home/End(min/max ends). - Roles: Correctly implements
role="slider". - States: Communicates current value via
aria-valuenow,aria-valuemin,aria-valuemax. - Touch: Provides a minimum 48x48dp touch target for interactive handles.
- Motion: Respects
prefers-reduced-motionuser preferences.
API Reference
Slider
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Controlled value. |
defaultValue | number | (min+max)/2 | Initial value for uncontrolled usage. |
onValueChange | (value: number) => void | — | Callback fired during dragging or keyboard adjustments. |
onValueChangeEnd | (value: number) => void | — | Callback fired when user releases thumb or commits keyboard input. |
min | number | 0 | Minimum allowed value. |
max | number | 100 | Maximum allowed value. |
step | number | 0 | Snap step increment. 0 disables snapping (continuous mode). |
orientation | "horizontal" | "vertical" | "horizontal" | Layout direction. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Physical track thickness per MD3 spec. |
variant | "primary" | "secondary" | "tertiary" | "error" | "primary" | MD3 color scheme. |
trackShape | "md3" | "full" | number | "md3" | Corner radius style for outer track ends. |
isCentered | boolean | false | When true, active track grows from center (50%) outward. |
showValueIndicator | boolean | false | Shows floating value tooltip on interaction. |
showTicks | boolean | false | Shows tick dot markers for discrete mode (step > 0). |
disabled | boolean | false | Disables interaction and applies 38% opacity. |
insetIcon | ReactNode | — | Leading icon rendered inside track (track size >= 40dp). |
insetIconAtMin | ReactNode | — | Alternate leading icon displayed when value === min. |
insetIconTrailing | ReactNode | — | Trailing icon rendered inside track. |
insetIconAtMax | ReactNode | — | Alternate trailing icon displayed when value === max. |
formatValue | (value: number) => string | String | Formatter function for displayed tooltip value. |
className | string | — | Additional CSS classes applied to outer container. |
aria-label | string | — | Accessible label for screen readers. |
aria-labelledby | string | — | ID of element labeling the slider. |
RangeSlider
Extends SliderProps (excluding value, defaultValue, onValueChange, onValueChangeEnd, and isCentered).
| Prop | Type | Default | Description |
|---|---|---|---|
value | [number, number] | — | Controlled [start, end] tuple. |
defaultValue | [number, number] | [min, max] | Initial [start, end] tuple for uncontrolled usage. |
onValueChange | (value: [number, number]) => void | — | Callback fired during range drag. Prevents thumb crossover. |
onValueChangeEnd | (value: [number, number]) => void | — | Callback fired when range drag ends. |