# Build a Custom Range Slider Component

Source: https://www.techinterview.org/post/3233475410/build-custom-range-slider-component/
Updated: 2026-06-30 · techinterview.org

"Build a range slider" is a frontend machine-coding question that punishes assumptions. The native `<input type="range">` works for simple cases but fails for dual-thumb (price ranges), custom styling, marks, snap points, and complex accessibility. The interview tests pointer math, ARIA pattern, and the keyboard mechanics that screen-reader users rely on.

## Clarify scope

- Single thumb or dual thumb (range)?

- Snap to discrete marks or continuous?

- Show labels or values?

- Vertical orientation in scope?

- Mobile UX (larger touch target)?

## The architecture

- Track: a styled div with width 100%

- Filled portion: another div with absolute positioning, width based on value

- Thumb: a button absolutely positioned

- Hidden inputs for form integration (single or two)

## Pointer interaction

- `pointerdown` on a thumb (or anywhere on track for tap-to-jump)

- Capture the pointer (`setPointerCapture`)

- Compute new value from pointer position relative to track

- Clamp to min/max; snap to step

- Update state; emit onChange

- On `pointerup`: release capture, emit onCommit

## Value-from-position math


```
const rect = trackRef.current.getBoundingClientRect();
const ratio = (event.clientX - rect.left) / rect.width;
const range = max - min;
const raw = min + ratio * range;
const stepped = Math.round((raw - min) / step) * step + min;
const clamped = Math.max(min, Math.min(max, stepped));
```


## Dual thumb — the range variant

- Two values: min and max

- Each thumb constrained: low cannot exceed high (and vice versa)

- Filled portion is between the two thumbs

- Drag a thumb past the other: swap roles

- Track click: move the closer thumb

## Keyboard interaction

- Tab focuses the thumb

- ArrowLeft / ArrowDown: decrease by step

- ArrowRight / ArrowUp: increase by step

- PageDown / PageUp: decrease / increase by larger amount

- Home / End: jump to min / max

- For dual thumb: each thumb is separately tabbable

## Accessibility — WAI-ARIA slider pattern

- Each thumb has `role="slider"`

- `aria-valuemin`, `aria-valuemax`, `aria-valuenow`

- `aria-valuetext` for non-numeric values ("Wednesday")

- `aria-orientation="horizontal"` (or vertical)

- Each thumb has a label (`aria-label` or `aria-labelledby`)

- For dual thumb: distinguish "minimum" and "maximum" labels

## Snap and marks

- Step value rounds to discrete positions

- Visible marks: render small dots or ticks at step positions

- Labels above marks for select positions (e.g., $0, $250, $500)

- "Magnetic" snap: gravitate toward marks during drag

## Touch considerations

- Touch target should be at least 44×44 px (WCAG)

- Visual thumb can be smaller; hit area can be larger via padding

- Show the value in a tooltip / bubble during drag for clarity

- Disable browser pinch / scroll on the track

## Visual states

- Default: thumb visible, neutral color

- Hover: subtle scale or shadow

- Focus: visible focus ring (do not remove the default outline without replacement)

- Active (dragging): bigger thumb, value tooltip

- Disabled: muted; pointer cursor disabled

## RTL handling

- Reverse the value-from-position math when dir="rtl"

- Or use CSS logical properties + a flipped track

## Complex inputs

For range with a histogram (Amazon-style price filter):

- Render histogram bars behind the track

- Bar height = count in that bucket

- Bars within selected range fully colored; outside dimmed

## Edge cases interviewers love

- User drags past viewport edge — pointer capture handles it

- Step does not divide range evenly — round and clamp

- Min === max — disabled thumbs, do not allow drag

- Both thumbs at same position — should snap one off the other

- Mouse leaves window during drag — keep tracking via capture

## Performance

- Use CSS transforms for thumb position, not `left`

- Throttle pointermove to rAF if doing expensive work

- Avoid React re-renders during drag if possible (manipulate DOM via ref)

## What separates senior from staff

Senior implementations get the slider pointer math right. Staff implementations handle the dual-thumb correctly, the keyboard pattern fully, the WAI-ARIA semantics, and the touch hit-area. Principal candidates raise the histogram variant and the value-formatting strategy (currency, dates, etc.).

## Frequently Asked Questions

### Library options for production?

Radix Slider, react-aria Slider (Adobe), rc-slider. Radix and react-aria are accessible by default; both are excellent.

### Should I support vertical orientation?

Rarely needed. If asked, swap the math (Y instead of X) and aria-orientation. Most products do not need vertical sliders.

### What about non-linear scales?

Logarithmic scales (e.g., for monetary ranges) need value-to-position and position-to-value functions. Provide them as props; the rest of the component is unchanged.
