UIDex

Slider

المنزلق

<input type="range">

also calledrange input، range slider، volume slider، dual range slider، track and thumb، value slider، سلايدر، الشريط المنزلق، شريط الانزلاق، منزلق المدى، مقبض القيمة، شريط تحديد القيمة

A slider is an input for picking one number out of a continuous range, and it expresses that choice as the position of a thumb along a track. The value changes during the drag rather than after it, which suits anything whose effect is visible on the spot: volume, font size, screen brightness, a price ceiling in a search filter. It is not a progress bar: a progress bar is output rather than input, it carries role="progressbar", and no part of it answers to a drag. It is not a scrollbar either, because a scrollbar moves a viewport over content longer than the window and the length of its thumb tells you how much of that content fits on screen, while a slider thumb keeps one size since only its position carries meaning. Nor is it a number field with steppers, which accepts typing and suits exact values the user already knows, whereas a slider trades precision for speed. The rule that settles it in review: if the user knows the number before reaching for the control, give them a field; if they are hunting for it by feel, give them a slider.

If you called it…

"the bar you drag to change the volume""the line with a dot you slide along""the price filter with two handles""that brightness thing in settings""drag to pick a number instead of typing it"

Live specimen

Interact with the demo. Every part is real and numbered.

الحدّ الأعلى للسعر
aria-valuenow="400" aria-valuemin="0" aria-valuemax="1000"وينطقها قارئ الشاشة «400 جنيه» لأن aria-valuetext يحمل الوحدة
RTLالمحور كلّه ينعكس: الصفر عند اليمين والامتلاء ينمو نحو اليسار

Anatomy: every part, named

Hover a row to locate it in the demo above.

Build prompt

Build a slider for picking one numeric value. Start from <input type="range"> with min, max and step whenever the visual design allows it, because it hands you keyboard support, the accessibility tree, and the RTL axis flip for free. If you have to hand-build it, make the thumb a focusable element carrying role="slider", either a real button or a div with tabindex="0", holding aria-valuemin, aria-valuemax and an aria-valuenow updated on every change. Give the control an accessible name from a <label> or aria-labelledby, and add aria-valuetext whenever the value is a unit, a currency, or a day name, because the bare number means nothing on its own. Show the value as permanent text beside the track instead of a bubble that appears only during a drag, and put aria-hidden on that text so the number is not read twice. Guarantee a 44x44 CSS pixel touch target around the thumb with padding while keeping the drawn disc small, and make a press on the track move the thumb to the pressed position. Set step to something the available track width can actually hit, and remember that it counts from min. If the slider has two thumbs, block crossing with a minimum number of steps between them and never let the lower value pass the upper one. Use logical properties everywhere: inset-inline-start, inline-size and padding-inline instead of left, width and padding-left. Centre the thumb with translateX(calc(-50% * var(--dir))) and set --dir to -1 under [dir="rtl"]. Compute the drag from the logical edge with (rect.right - clientX) when the direction is rtl instead of (clientX - rect.left), and flip the meaning of ArrowLeft and ArrowRight with the same sign while leaving ArrowUp, ArrowDown, Home and End alone. Separate the live change during the drag from the final commit on release, so no request goes to the server for every pixel.

How it behaves right-to-left

This section is ours alone.

The whole axis reverses

mirrors

A horizontal slider is one long inline axis, so all of it turns over in Arabic: the minimum sits at the right, the maximum at the left, and the fill grows leftward. A native <input type="range"> does this on its own because it follows the computed direction of the input element itself, not of the page, and that is where the trap is: any wrapper forcing dir="ltr" around the field (a common habit with numbers and numeric fields) leaves an English-facing slider inside an Arabic page. A custom slider built on left and width knows nothing about direction and comes out of the flip exactly as it went in.

Anchoring the fill and the thumb

mirrors

Swap left: 0; width: 40% for inset-inline-start: 0; inline-size: 40% and the fill mirrors by itself, because the edge it is anchored to is the one that moved. Animating it with transform: scaleX() buys you none of that, since every transform-origin keyword is physical (left, right, top, bottom, center) alongside lengths and percentages, with no logical counterpart anywhere in the property, so the flip costs you a transform-origin: right inside [dir="rtl"] or a direction variable. Centring the thumb is the last trap: inset-inline-start: 40% puts its right edge 40% from the right of the track in RTL, where the usual translateX(-50%) shifts it the wrong way by half its width, so write translateX(calc(-50% * var(--dir))) with --dir set to -1 under [dir="rtl"].

The arrow keys swap meaning

mirrors

The two horizontal arrows trade places with the axis, so in Arabic ArrowLeft raises the value and ArrowRight lowers it, and a native range input already does this because it reverses the keys along with its rendering. The APG keyboard table for the slider pattern is written from a left to right point of view and never mentions the reversal, which is why hand-written handling like switch (e.key) { case "ArrowRight": v + step } keeps getting copied and keeps handing the Arabic user the opposite of the key pressed. Nothing else on the keyboard moves: ArrowUp raises and ArrowDown lowers in both directions, and Home and End jump to the logical minimum and maximum rather than to the right and left of the screen.

The pointer maths

mirrors

Pointer events have no notion of direction: clientX is a physical coordinate, and getBoundingClientRect() reports left and right physically whatever dir says. So the usual (e.clientX - rect.left) / rect.width measures from the physical left edge, which in Arabic is the maximum end, and the value comes out inverted with the thumb jumping to the far side on first touch. Measure from the logical start with (rect.right - e.clientX) / rect.width when getComputedStyle(track).direction === "rtl", and set touch-action: none on the track so page scrolling does not swallow the drag on mobile.

The number itself does not flip

never mirrors

The axis reverses, the number does not. Digits run left to right inside Arabic text under the bidi algorithm, so never reorder their characters and never flip the label with scaleX(-1). The real problem is the symbols glued to them: a negative value such as -5 renders as 5- inside an Arabic paragraph because the minus resolves to a neutral and takes the paragraph direction, and a Latin unit like km slides to the wrong side of the digits the same way, so isolate every value label in a <bdi> or with unicode-bidi: isolate and generate the text with Intl.NumberFormat so the decimal separator and the currency symbol come out the way the locale expects.

In code

Each row is one framework's word for the same thing. Take the row your project speaks.

HTML<input type="range" min max step>The native control; keyboard, the ARIA values, and the RTL axis flip all arrive with it.
ARIArole="slider" + aria-valuenowWhat you owe the user when you rebuild it from divs, along with valuemin, valuemax and a name.
Radix UI<Slider.Root value={[a, b]}>The value is an array, which is how one component covers both one thumb and two; minStepsBetweenThumbs blocks the crossing and onValueCommit fires once on release.
Material UI<Slider marks disableSwap />marks takes true, which spaces the marks by step, or an array of value and label; disableSwap keeps the dragged thumb from passing its neighbour.
Ant Design<Slider range tooltip={{ formatter }} />One prop, range, turns a single thumb into two and the value into a pair; tooltip is an object, not a boolean flag.
CSSinset-inline-start + inline-sizeThe pair that lets the fill and the thumb mirror without a single [dir="rtl"] override.
SwiftUISlider(value: $v, in: 0...100, step: 1)Apple gives you one thumb only; a two-handle range is a control you build yourself.

See also

Updated · 2026-08-19