Components · Inputs
Slider
BetaPicks a value in a range, such as target RPE.
- Source
components/ui/slider.tsx - shadcn/ui Slider
- Figma: not linked yet (Q13)
How hard the last set should feel. 10 means no reps left.
When to use
- To set a value on a known scale where the rough position matters more than the exact number: target RPE, session length, rest time.
- To set a range with two thumbs: a daily carb range, a price range for packages.
- Always with a visible label and the current value next to it.
When not to use
- When the exact number matters, or the range is large (a body weight, a price): use an Input with a unit. Coaches can type 82.5 faster than they can drag to it.
- For settings with a few named steps ("Beginner, Intermediate, Advanced"): use a Radio group or a Select.
- To set values across a grid such as sets × weeks: that is the Matrix slider.
- To show progress: use a progress bar. A slider is always an input.
Anatomy
- 1. Label: names the value, above the slider
- 2. Value: the current value and its unit, at the end of the label row
- 3. Slider: the track (muted), the range fill (primary) from the start to the thumb, and the thumb
Variants
- Single value: one thumb; the range fills from the start of the track to the thumb.
- Range: pass two values (
[150, 220]) for two thumbs; the fill sits between them.minStepsBetweenThumbskeeps them apart. - Stepped:
stepsets the increment: 0.5 for RPE, 5 for minutes, 10 for grams. Pick the step coaches would write down.
Vertical sliders (orientation="vertical") are supported but not used in coach-admin today.
States
Set by the program template.
| State | What changes | Token |
|---|---|---|
| Default | Muted track, primary range, thumb with a primary border | bg-muted, bg-primary, border-primary |
| Hover, focus-visible (thumb) | A 4px ring around the thumb | ring-ring/50, ring-4 |
| Dragging | Thumb follows the pointer; the value updates as it moves | — |
| Disabled | Whole slider at half opacity, thumbs can't be focused | opacity-(--opacity-disabled) |
There is no invalid state: the slider can't hold a value outside min and max. Put limits in the scale, not in an error message.
Each thumb carries role="slider", so the component forwards aria-label or aria-labelledby to the thumbs. A range slider names each thumb with thumbLabels ("Minimum carbs", "Maximum carbs"), and getValueText sets what a screen reader speaks for the value ("120 g"). Upstream shadcn put the label on the root, where screen readers don't read it; this version fixes that.
Usage guidelines
- Use a scale that fits the decision: RPE 1–10 in steps of 0.5, not 0–100.
- Keep the default on a sensible value (RPE 8, 45 minutes), not at the minimum.
- Save on release (
onValueCommit) when a change triggers a request; update the visible value on every move (onValueChange). - Pair a slider with an input when coaches sometimes need an exact number.
Content guidelines
- Label: the thing being set, sentence case: "Target RPE", "Session length", "Daily carbs".
- Value: digits and a unit, as on the Content page: "45 min", "150–220 g", "90 s". Use an en dash in ranges.
- If the scale needs explaining, add one line of help text below: "10 means no reps left."
- Arabic: units follow the number in Arabic ("45 دقيقة", "150–220 غ"), with Western digits (QUESTIONS Q22).
Accessibility
| Key | Does |
|---|---|
| Tab | Moves to the thumb (each thumb of a range is a separate stop) |
| Arrow right / up | Increases by one step (in RTL, Arrow left increases) |
| Arrow left / down | Decreases by one step |
| Shift + Arrow, Page up / down | Changes by 10 steps |
| Home / End | Jumps to the minimum or maximum |
- Each thumb has
role="slider"witharia-valuemin,aria-valuemaxandaria-valuenow. Once the naming gap above is fixed, a screen reader announces "Target RPE, slider, 8". - Numbers alone can be unclear: pass a value text with the unit ("45 minutes") so the announcement matches the screen.
- The thumb is 16px. It meets WCAG 2.5.8 through the spacing exception, so keep other targets at least 24px from its center. Pointer users can also click anywhere on the track.
- Changes are announced as the value changes; don't add a live region for the visible value too, or screen readers read it twice.
RTL
- The track fills from the right: the minimum is on the right and the range grows to the left. Radix reads the direction from the provider.
- Arrow keys follow the visual direction: Arrow left increases in RTL. Up and down don't change.
- The label stays at the start of the row and the value at the end, so they swap sides too.
- Numbers keep Western digits and read left to right inside the Arabic value ("150–220 غ").
Tokens used
Read from components/ui/slider.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --background | bg-background | white | white |
| --muted | bg-muted | neutral-100 | neutral-100 |
| --primary | bg-primary border-primary | brand-600 | orange-500 |
| --ring | ring-ring/50 | brand-600 | orange-500 |
- Opacity
opacity-(--opacity-disabled) - Radius
rounded-full - Shadow
shadow-sm
Props
<Slider>
Also takes every prop of Radix Slider.Root
| Prop | Type | Default |
|---|---|---|
| thumbLabels? | string[] | — |
| getValueText? | (value: number) => string | — |
Replaces in coach-admin
From the read-only audit of coach-admin: 27 style values, 29 uses.
coach-admin has one reusable slider (src/reusableComponents/slider/, el-slider classes) with a value tooltip above the thumb. The tooltip becomes the value next to the label. The program matrix slider is separate: see Matrix slider.
Classes and selectors
.el-slider-tooltip-arrow5.el-slider-tooltip-content4.el-slider-thumb3.el-slider-track2.el-slider-thumb:focus2.plan__slider-labels2.el-slider-tooltip-desc1.el-slider-tooltip1
Files
src/reusableComponents/slider/slider.scss26src/reusableComponents/slider/Slider.tsx1
The inventory lists the first 12 locations of each value, so file counts are a lower bound.