Build a Custom Range Slider Component

“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

  1. pointerdown on a thumb (or anywhere on track for tap-to-jump)
  2. Capture the pointer (setPointerCapture)
  3. Compute new value from pointer position relative to track
  4. Clamp to min/max; snap to step
  5. Update state; emit onChange
  6. 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.

Scroll to Top