Inputs

Switch-to-Slider Morph

A React switch and slider that share one thumb: turning it on makes the thumb leap onto a vertical or horizontal slider to set a value, turning it off sends it back. Built for enable-then-adjust settings.

A setting that is first switched on and then tuned needs only one thumb. While off, the thumb sits in the switch; press it and the slider track unfolds while the thumb springs across, growing slightly mid-flight as if it leapt, lands on min and then counts up to the current value. Once on, drag the thumb or click the track to adjust, and a value bubble with rolling digits follows the thumb while you drag; press the switch again and the thumb flies back before the track folds away. The on/off state and the value each support controlled and uncontrolled modes, and the slider can be vertical or horizontal.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add switch-to-slider-morph.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

500
28
100
Active edited usageDefault props
<SwitchToSliderMorph/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/switch-to-slider-morph.en.json

Or, once registries are configured in components.json, install it as @webberui/switch-to-slider-morph.

Usage

import { SwitchToSliderMorph } from "@/components/ui/switch-to-slider-morph";

const [on, setOn] = React.useState(false);
const [level, setLevel] = React.useState(72);

<SwitchToSliderMorph
  label="Focus mode"
  enabled={on}
  onEnabledChange={setOn}
  value={level}
  onValueChange={setLevel}
  valueLabel={(v) => `Noise ${v}%`}
/>

Props

PropTypeDefaultDescription
enabledboolean—Controlled on/off state; when omitted the component manages it internally (uncontrolled)
defaultEnabledbooleanfalseInitial on/off state in uncontrolled mode
onEnabledChange(enabled: boolean) => void—Callback when the on/off state changes
valuenumber—Controlled slider value; when omitted the component manages it internally (uncontrolled)
defaultValuenumberminInitial value in uncontrolled mode
onValueChange(value: number) => void—Callback when the value changes (already snapped to step and clamped to min–max)
minnumber0Minimum value
maxnumber100Maximum value
stepnumber1Value increment
orientation"vertical" | "horizontal""vertical"Slider orientation
lengthnumber160Slider track length (px)
labelstring"啟用" (Enable)Label text, also the accessible name of both the switch and the slider
valueLabel(value: number) => string(v) => `${v}%`Value formatter, also used for aria-valuetext
countUpbooleantrueOn enable, the thumb lands on min first, then counts up to the current value
stiffnessnumber500Stiffness of the thumb's flight spring
dampingnumber28Damping of the thumb's flight spring
disabledbooleanfalseDisable the switch and the slider
classNamestring—Passed through to the outermost container

How it works

  • Shared thumb: the thumb in the switch and the thumb on the slider are the same motion layoutId (inside a LayoutGroup), so toggling springs it from the old place to the new one. Layout animation travels in a straight line, so an extra 1 → 1.2 → 1 scale mid-flight makes it read as a leap.
  • The slider track unfolds with clip-path rather than a real height change — the thumb's landing spot is measured during render, and a zero-size track would send it to the wrong place. When turning off, the track waits about 200ms before folding so the thumb flies home first.
  • Dragging moves the thumb with a transform offset, which does not trigger layout animation, so it tracks the pointer without lag; setPointerCapture keeps tracking even outside the track, and clicking anywhere on the track jumps to that value.
  • The count-up only happens at the moment of enabling and only when the value is above min; dragging or a key press stops it immediately.
  • CSS variables: --wb-knob-slider-accent (default #3b82f6, switch and fill colour), --wb-knob-slider-expand (default 220ms, track unfold duration), --wb-knob-slider-count (default 600ms, count-up duration).

Accessibility

  • The switch is a role="switch" button with aria-checked; the slider thumb is role="slider" with aria-valuenow / aria-valuemin / aria-valuemax / aria-orientation, and aria-valuetext uses the output of valueLabel
  • Both point at label through aria-labelledby; while off, the slider track is hidden from assistive technology
  • Keyboard: arrow keys ±step, PageUp / PageDown ±10 steps, Home / End jump to min / max; the switch and the thumb both show a focus-visible ring
  • Turning it on with the switch moves focus to the slider thumb, turning it off returns focus to the switch; controlled changes from outside never steal focus
  • The value bubble and the readout next to the label are visual only (aria-hidden); semantics come from aria-valuetext
  • When the user's system has reduce motion turned on, the thumb does not leap or count — it fades in at its new place over 120ms, and the track fades instead of unfolding