suiss/uim · React Native Component
Slider
A slider for picking a single value or a range by dragging one or more thumbs along a track.
Slider is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import { Slider } from "@/components/ui/slider"
export function SliderExample() {
return (
<Slider defaultValue={50} max={100} step={1} accessibilityLabel="Volume" />
)
}import EsuissUI
import SwiftUI
struct SliderExample: View {
var body: some View {
SliderDemo(initial: 50)
}
}
struct SliderDemo: View {
var bounds: ClosedRange<Double> = 0...100
var step: Double = 1
@State private var value: Double
init(initial: Double, bounds: ClosedRange<Double> = 0...100, step: Double = 1) {
self.bounds = bounds
self.step = step
_value = State(initialValue: initial)
}
var body: some View {
ValueSlider(value: $value, in: bounds, step: step)
.accessibilityLabel(Text("Volume"))
.frame(maxWidth: .infinity)
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Slider
@Composable
fun SliderExample() {
SliderUncontrolled(50f)
}
@Composable
fun SliderUncontrolled(
initial: Float,
valueRange: ClosedFloatingPointRange<Float> = 0f..100f,
step: Float = 1f,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableFloatStateOf(initial) }
Slider(
value,
{ value = it },
Modifier.fillMaxWidth(),
enabled = enabled,
valueRange = valueRange,
step = step,
contentDescription = "Volume",
)
}Slider renders a thin track, a filled indicator and one thumb per value. Pass a number for a single value, or an array for a range or several thumbs. The active thumb widens while it is dragged. It works horizontally or vertically.
Thumbs are aligned to the edges of the track, so they stay within it at min and max.
Installation
$ pnpm dlx @esuiss/uim@latest add slider$ pnpm dlx @esuiss/uim@latest add slider --platform swift$ pnpm dlx @esuiss/uim@latest add slider --platform kotlinManual
- Install the dependencies:
npx expo install cn-
Add the pieces it builds on: the primitives
components/primitives/slider.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/slider.tsx:
import { cn } from "cn"
import * as SliderPrimitive from "@/components/primitives/slider"
import { resolveClassName } from "@/components/primitives/use-render"
function thumbIndexes(
value: SliderPrimitive.SliderValue | undefined,
defaultValue: SliderPrimitive.SliderValue | undefined
) {
const source = Array.isArray(value) ? value : defaultValue
const count = Array.isArray(source) ? source.length : 1
return Array.from({ length: count }, (_, index) => index)
}
function Slider<
Value extends SliderPrimitive.SliderValue = SliderPrimitive.SliderValue,
>({- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn-
Add the pieces it builds on: the primitives
components/primitives/slider.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/slider.tsx:
import { cn } from "cn"
import * as SliderPrimitive from "@/components/primitives/slider"
import { resolveClassName } from "@/components/primitives/use-render"
function thumbIndexes(
value: SliderPrimitive.SliderValue | undefined,
defaultValue: SliderPrimitive.SliderValue | undefined
) {
const source = Array.isArray(value) ? value : defaultValue
const count = Array.isArray(source) ? source.length : 1
return Array.from({ length: count }, (_, index) => index)
}
function Slider<
Value extends SliderPrimitive.SliderValue = SliderPrimitive.SliderValue,
>({- Update the import paths to match your project.
-
Add the pieces it builds on: the primitives
components/primitives/slider.tsx,components/primitives/use-render.tsx. -
Copy the source into
components/ui/slider.tsx:
import { StyleSheet } from "react-native"
import * as SliderPrimitive from "@/components/primitives/slider"
import { shadow, useTheme } from "@/components/ui/theme"
const styles = StyleSheet.create({
rootHorizontal: { width: "100%" },
rootVertical: { height: "100%" },
controlHorizontal: {
position: "relative",
height: 32,
width: "100%",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
controlVertical: {- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Copy the theme files every view uses:
import SwiftUI
import UIKit
struct RGBA: Sendable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(_ hex: UInt32, _ alpha: CGFloat = 1) {
red = CGFloat((hex >> 16) & 0xFF) / 255
green = CGFloat((hex >> 8) & 0xFF) / 255
blue = CGFloat(hex & 0xFF) / 255
self.alpha = alpha
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
import SwiftUI
public struct ValueSlider<Value: BinaryFloatingPoint>: View where Value.Stride: BinaryFloatingPoint {
@Binding var value: Value
let bounds: ClosedRange<Value>
let step: Value.Stride?
let onEditingChanged: (Bool) -> Void
public init(
value: Binding<Value>,
in bounds: ClosedRange<Value> = 0...1,
step: Value.Stride? = nil,
onEditingChanged: @escaping (Bool) -> Void = { _ in }
) {
_value = value
self.bounds = boundsimport SwiftUI
extension EnvironmentValues {
@Entry public var hapticsEnabled = true
}
extension View {
public func haptics(_ enabled: Bool) -> some View {
environment(\.hapticsEnabled, enabled)
}
func hapticFeedback<Trigger: Equatable>(_ feedback: SensoryFeedback, trigger: Trigger) -> some View {
modifier(HapticFeedbackModifier(feedback: feedback, trigger: trigger) { _, _ in true })
}
func hapticFeedback<Trigger: Equatable>(- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
package com.esuiss.ui.components
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composablepackage com.esuiss.ui.components
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalContentStyle = compositionLocalOf { TextStyle.Default }
val LocalContentColor = compositionLocalOf { Color.Unspecified }
val LocalIconSize = compositionLocalOf { 20.dp }package com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextpackage com.esuiss.ui.components
import android.provider.Settings
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Sizepackage com.esuiss.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
val LocalHapticsEnabled = staticCompositionLocalOf { true }
@Composable
fun ProvideHaptics(enabled: Boolean, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalHapticsEnabled provides enabled, content = content)
}package com.esuiss.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
@Composablepackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.theme.EsuissTheme
@Composable
fun Separator(- Update the
com.esuiss.uipackage names to match your project.
Usage
import { Slider } from "@/components/ui/slider"<Slider defaultValue={50} max={100} step={1} accessibilityLabel="Volume" />Use defaultValue for an uncontrolled slider, or value with onValueChange to control it. onValueCommitted runs when a drag or track press ends, which suits saving or fetching.
Set min, max and step to define the range; min defaults to 0 and max to 100. For ranges, minStepsBetweenValues keeps thumbs apart and thumbCollisionBehavior decides whether thumbs push each other, swap, or stop (none).
The number of thumbs comes from the array length of value or defaultValue.
Examples
Controlled
A controlled slider that shows the live and committed values.


import * as React from "react"
import { View } from "react-native"
import { Slider } from "@/components/ui/slider"
import { Text } from "@/components/ui/text"
function ControlledDemo() {
const [value, setValue] = React.useState(30)
const [committed, setCommitted] = React.useState(30)
return (
<View className="w-full gap-3">
<View className="flex-row items-center justify-between">
<Text className="text-sm font-medium">Brightness</Text>
<Text className="text-sm text-label-secondary">
{value} / committed {committed}
</Text>
</View>
<Slider
value={value}
onValueChange={(next) => setValue(next)}
onValueCommitted={(next) => setCommitted(next)}
accessibilityLabel="Brightness"
/>
</View>
)
}
export function SliderControlledExample() {
return (
<ControlledDemo />
)
}import EsuissUI
import SwiftUI
struct SliderControlledDemo: View {
@State private var value: Double = 30
@State private var committed: Double = 30
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Brightness")
.textStyle(.sm, weight: .medium)
.foregroundStyle(Color.label)
Spacer()
Text(verbatim: "\(Int(value)) / committed \(Int(committed))")
.textStyle(.sm)
.foregroundStyle(Color.labelSecondary)
}
ValueSlider(value: $value, in: 0...100, step: 1) { editing in
if !editing { committed = value }
}
.accessibilityLabel(Text("Brightness"))
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Slider
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
import kotlin.math.roundToInt
@Composable
fun SliderHeader(title: String, detail: String) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Text(title, style = Typography.sm.copy(fontWeight = FontWeight.Medium))
Text(detail, style = Typography.sm, color = EsuissTheme.colors.labelSecondary)
}
}
@Composable
fun SliderControlled() {
var value by rememberSaveable { mutableFloatStateOf(30f) }
var committed by rememberSaveable { mutableFloatStateOf(30f) }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
SliderHeader("Brightness", "${value.roundToInt()} / committed ${committed.roundToInt()}")
Slider(
value,
{ value = it },
Modifier.fillMaxWidth(),
onValueChangeFinished = { committed = value },
contentDescription = "Brightness",
)
}
}Range
A two-thumb price range with a step and a minimum gap.


import * as React from "react"
import { View } from "react-native"
import { Slider } from "@/components/ui/slider"
import { Text } from "@/components/ui/text"
function RangeDemo() {
const [value, setValue] = React.useState<readonly number[]>([25, 75])
return (
<View className="w-full gap-3">
<View className="flex-row items-center justify-between">
<Text className="text-sm font-medium">Price</Text>
<Text className="text-sm text-label-secondary">
${value[0]} – ${value[1]}
</Text>
</View>
<Slider
value={value}
onValueChange={setValue}
step={5}
minStepsBetweenValues={2}
accessibilityLabel="Price"
/>
</View>
)
}
export function SliderRangeExample() {
return (
<RangeDemo />
)
}import EsuissUI
import SwiftUI
struct SliderRangeDemo: View {
@State private var value: ClosedRange<Double> = 25...75
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Price")
.textStyle(.sm, weight: .medium)
.foregroundStyle(Color.label)
Spacer()
Text(verbatim: "$\(Int(value.lowerBound)) – $\(Int(value.upperBound))")
.textStyle(.sm)
.foregroundStyle(Color.labelSecondary)
}
RangeSlider(value: $value, in: 0...100, step: 5)
.accessibilityLabel(Text("Price"))
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.RangeSlider
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
import kotlin.math.roundToInt
@Composable
fun SliderHeader(title: String, detail: String) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Text(title, style = Typography.sm.copy(fontWeight = FontWeight.Medium))
Text(detail, style = Typography.sm, color = EsuissTheme.colors.labelSecondary)
}
}
@Composable
fun SliderRange() {
var start by rememberSaveable { mutableFloatStateOf(25f) }
var end by rememberSaveable { mutableFloatStateOf(75f) }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
SliderHeader("Price", "$${start.roundToInt()} – $${end.roundToInt()}")
RangeSlider(
start..end,
{
start = it.start
end = it.endInclusive
},
Modifier.fillMaxWidth(),
step = 5f,
contentDescription = "Price",
)
}
}Multiple thumbs
Three thumbs that swap when they cross.
import { Slider } from "@/components/ui/slider"
export function SliderMultipleThumbsExample() {
return (
<Slider defaultValue={[10, 40, 80]} max={100} step={1} accessibilityLabel="Volume" thumbCollisionBehavior="swap" />
)
}Step
A slider that moves in decimal steps.


import { Slider } from "@/components/ui/slider"
export function SliderStepExample() {
return (
<Slider defaultValue={0.5} max={1} step={0.1} accessibilityLabel="Volume" min={0} />
)
}import EsuissUI
import SwiftUI
struct SliderStepExample: View {
var body: some View {
SliderDemo(initial: 0.5, bounds: 0...1, step: 0.1)
}
}
struct SliderDemo: View {
var bounds: ClosedRange<Double> = 0...100
var step: Double = 1
@State private var value: Double
init(initial: Double, bounds: ClosedRange<Double> = 0...100, step: Double = 1) {
self.bounds = bounds
self.step = step
_value = State(initialValue: initial)
}
var body: some View {
ValueSlider(value: $value, in: bounds, step: step)
.accessibilityLabel(Text("Volume"))
.frame(maxWidth: .infinity)
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Slider
@Composable
fun SliderStepExample() {
SliderUncontrolled(0.5f, valueRange = 0f..1f, step = 0.1f)
}
@Composable
fun SliderUncontrolled(
initial: Float,
valueRange: ClosedFloatingPointRange<Float> = 0f..100f,
step: Float = 1f,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableFloatStateOf(initial) }
Slider(
value,
{ value = it },
Modifier.fillMaxWidth(),
enabled = enabled,
valueRange = valueRange,
step = step,
contentDescription = "Volume",
)
}Vertical
Single and range sliders in vertical orientation.


import { View } from "react-native"
import { Slider } from "@/components/ui/slider"
export function SliderVerticalExample() {
return (
<View className="h-48 flex-row justify-center gap-8">
<Slider defaultValue={40} orientation="vertical" />
<Slider defaultValue={[20, 70]} orientation="vertical" />
</View>
)
}Disabled
A disabled slider with muted colors.


import { Slider } from "@/components/ui/slider"
export function SliderDisabledExample() {
return (
<Slider defaultValue={40} max={100} step={1} accessibilityLabel="Volume" disabled />
)
}import EsuissUI
import SwiftUI
struct SliderDisabledExample: View {
var body: some View {
SliderDemo(initial: 40)
.disabled(true)
}
}
struct SliderDemo: View {
var bounds: ClosedRange<Double> = 0...100
var step: Double = 1
@State private var value: Double
init(initial: Double, bounds: ClosedRange<Double> = 0...100, step: Double = 1) {
self.bounds = bounds
self.step = step
_value = State(initialValue: initial)
}
var body: some View {
ValueSlider(value: $value, in: bounds, step: step)
.accessibilityLabel(Text("Volume"))
.frame(maxWidth: .infinity)
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.Slider
@Composable
fun SliderDisabledExample() {
SliderUncontrolled(40f, enabled = false)
}
@Composable
fun SliderUncontrolled(
initial: Float,
valueRange: ClosedFloatingPointRange<Float> = 0f..100f,
step: Float = 1f,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableFloatStateOf(initial) }
Slider(
value,
{ value = it },
Modifier.fillMaxWidth(),
enabled = enabled,
valueRange = valueRange,
step = step,
contentDescription = "Volume",
)
}API Reference
Slider
SliderPrimitive.RootProps<Value>Accessibility
- Each thumb has the
adjustablerole with its min, max and current value, so VoiceOver and TalkBack users can swipe up or down to change it. accessibilityLabelonSlideris passed to every thumb; set it, since the slider has no visible label.- Disabled thumbs report
disabledin their accessibility state.
Notes
- The track uses
bg-label-quaternaryand the indicatorbg-accent, withbg-control-disabledandbg-accent-disabledwhen disabled. - Thumbs use
bg-surfacewith a shadow, orbg-labelin dark mode. - Light and dark colors follow the system appearance.