Docs
Inputs

Range Slider

A dual-thumb slider: glass bubble thumbs joined by a tinted span, readout chips that merge as they meet, and steps that tick.

$npx swiftpieces add RangeSlider
See it in a Pro app On a real screen in one of the Pro apps: open it with Pro, take it apart, remix it, and copy the SwiftUI.
Free · MIT + Commons ClauseiOS 17.0+Liquid Glasssliderrangedual thumbfilterpricehaptic
Type
RangeSlider
Files
RangeSlider.swift
Depends on
Nothing (Apple frameworks only)
Version
1.2.0

Liquid glass dual-thumb slider that binds a ClosedRange of any floating-point type. The thumbs are glass bubbles and the selected span is tinted glass joining them, so the three move as one liquid shape. Each readout chip rests joined above its thumb; when the two would touch, one melts into the other and the survivor reads the whole range. The simplest call is one line: RangeSlider(value: $price, in: 0...1000, step: 10, format: .currency(code: "USD").precision(.fractionLength(0))). Pass a FormatStyle or a (V) -> String closure as format; it drives the readouts and the VoiceOver values.

Notes

  • Generic over any BinaryFloatingPoint (Double, Float, CGFloat). Bind a ClosedRange; pass format: as a FormatStyle (.currency(code: "USD").precision(.fractionLength(0)), .percent) or a (V) -> String closure. The format drives the readouts and the VoiceOver values.
  • The slider is one liquid glass shape: the thumbs are clear glass bubbles, and the selected span is tinted glass resting a liquid neck's width from each of them, so it joins them through necks. When the thumbs come close the span gives way and they neck straight into each other. Outside the span the thumbs ride a quiet line that is not glass, so nothing else merges with them.
  • Drag either thumb, or touch the track and the nearest thumb glides to the finger in one quick move, landing with a little give, while your finger's own movement stays 1:1 on top of it. Grabbing a thumb keeps the finger's offset, so it never jumps under the touch, even while it is still landing from the last release. When both thumbs sit on the same point, the first clear movement decides: right (in left-to-right) takes the upper thumb, left takes the lower.
  • Tracking is never animated: the thumbs follow the finger directly, and only the glide from a track touch and the release spring. Each edge of the span rides its own thumb, so the span stays joined to both through any glide or release. A grabbed thumb lifts: it swells on a quick spring with no overshoot, and drops back with a little give when you let go.
  • The thumbs never cross. They stop minimumDistance apart, kept on the step grid (a 5 gap with step 10 stops at 10). Against the other thumb a thumb stops firm, with no give, and a touch on the track that lands there arrives without overshoot.
  • Past either end of the track the thumb gives with rubber-band resistance, never more than about its own width (24 to 32pt), while the value stays at the bound. On release it springs back at the speed it was moving. While pulled it draws past the slider's edge, so leave room beside it rather than clipping it.
  • step snaps from bounds.lowerBound. Both bounds stay reachable when the span is not a multiple of the step (0...1000 by 30 reaches 1000). Values are rounded to the step's decimal places, so 0.1 steps write 0.3, not 0.30000000000000004. Out-of-bounds or reversed bindings are shown clamped and written back clean on the first change. While you drag, the value moves step by step and the thumb stays under your finger; when you let go anywhere on the track it snaps onto its step from rest, so its give is a sliver of the distance left and never reaches a neighboring value. Only a thumb pulled past the end keeps its speed as it springs back.
  • Readouts: .thumbs (default) rests a glass chip a liquid neck's width above each thumb, joined to it and clamped inside the track. A grabbed thumb's chip takes the span's tint a beat after the thumb lifts. When the two chips would come within 6pt, the right one melts into the left, which widens to read "lower – upper" around the thumbs' midpoint, and it buds back out once they are 12pt apart again, so a thumb resting at the threshold never flickers. .header prints "lower – upper" above the track and stacks the values at large text sizes; after a VoiceOver step or an animated change from your code its digits roll without overshoot. .hidden shows nothing.
  • Haptics: .selection on each value change while dragging (every 5% of the span without a step), throttled to about 30 per second; a rigid .impact once when a thumb reaches a bound or the minimum gap. Nothing plays on press, release or the landing on a step.
  • Right-to-left: the track mirrors, so the lower thumb sits on the right, and the merged chip reads in the reader's direction.
  • VoiceOver: two adjustable elements, "Minimum" then "Maximum" (set lowerLabel and upperLabel, e.g. "Minimum price"). Swipe up or down moves one step to the next valid value, or 1% of the span without a step, and the thumb, the span's edge and the chip glide there on the snap spring, landing with the same give as a release.
  • Thumbs keep a 44pt touch and VoiceOver target even when Style.thumbSize draws them smaller. Readouts use text styles and grow with Dynamic Type; all text is semibold.
  • Style sets the line, the span's tint (fill, the brand red by default), the thumbs' and chips' glass tints (neutral glass by default), ink, labels, track height and thumb size. thumbBorder no longer draws anything: the thumbs are glass bubbles with no ring. .disabled(true) desaturates, dims and blocks input.
  • Reduce Motion: a grabbed thumb keeps its size and takes the span's tint instead of swelling, and its chip still fills; the ends of the track stop the thumb without rubber-band give, and glides and landings settle without overshoot. Thumbs follow the finger directly either way. As the chips merge, the right one closes where it is instead of melting across, and the header's digits crossfade instead of rolling. Reduce Transparency draws the glass as solid shapes.
  • The drag starts on touch-down so the track responds to taps; inside a vertical ScrollView or sheet, a drag that begins on the slider moves the slider rather than scrolling.
  • For a full filter sheet built around it, see the filter sheet in SwiftPieces Pro.

Usage

RangeSliderExample()

Parameters

ParameterDescription
valueBound selected range. Values outside bounds are shown clamped; the first change writes a clean, in-bounds range back.
boundsMinimum and maximum selectable values. Both are always reachable, even when the span is not a multiple of step.
stepOptional snapping increment, measured from bounds.lowerBound. Snapped values are rounded to the step's decimal places, so 0.1 steps give 0.3, never 0.30000000000000004. Without a step the values are continuous.
minimumDistanceSmallest gap the two values keep. The thumbs stop against each other at this distance instead of crossing. Capped at the width of bounds.
readoutWhere the values print: .thumbs (glass chips joined above each thumb that melt into one when they would touch), .header (a "lower – upper" line above the track), or .hidden.
lowerLabelVoiceOver label of the lower thumb. Defaults to "Minimum".
upperLabelVoiceOver label of the upper thumb. Defaults to "Maximum".
styleColors and metrics. Defaults to the SwiftPieces house palette (a brand red span joining neutral glass thumbs over a quiet line), adapting to light and dark.
formatTurns a value into the text shown in the readouts and spoken by VoiceOver. A FormatStyle (such as .currency(code:) or .percent) or a closure. Defaults to a plain number with as many decimals as step has (up to two without a step).

Install

Pick one. Run it from the folder that contains your .xcodeproj and the files land inside your app.

Terminal

$npx swiftpieces add RangeSlider

Or ask your coding agent · Claude Code, Cursor or Xcode, once the MCP server is connected

>Add the SwiftPieces "Range Slider" piece to my app

Or copy the source above into your app.

New to SwiftUI?If you know React, CSS or Tailwind, SwiftUI is easier to pick up than it looks.Try real app patterns first

Building a whole app? See SwiftPieces Pro →

All inputs and forms →

Build it yourself: SwiftUI haptics guide

On this page

SwiftPiecesPro

Build the whole app with one library.

Production-ready screens, whole flows, complete app templates, a Build Kit your agent follows and Pro remixing.

54 screens · 46 patterns · 48 flows · 12 templates · 31 Build Kit skills

Explore Pro