Switch

An on/off control whose thumb settles with a double bounce.

Installation

npx shadcn@latest add @loomui/switch

Usage

import { Switch } from "@/components/ui/switch"
// Uncontrolled
<Switch defaultChecked aria-label="Auto-send" />
 
// Controlled
<Switch checked={autoSend} onCheckedChange={setAutoSend} aria-label="Auto-send" />

How it works

The thumb's travel distance is never a magic number — --travel is calc()d from the track width, thumb diameter, and padding, so resizing any of those custom properties keeps the thumb landing exactly on the track's edge.

The bounce only plays once the switch has actually been clicked. A fresh data-checked — from defaultChecked, a controlled checked prop, anything that isn't a click — renders in its final position immediately, with no data-interacted attribute yet, so nothing animates. The first click sets data-interacted and it stays set, so every toggle after that — click-driven or prop-driven — eases the thumb past its resting spot and settles back, rather than snapping straight there.

Props

PropTypeDefaultDescription
checkedboolean-Controlled state. Omit for uncontrolled.
defaultCheckedbooleanfalseInitial state when uncontrolled.
onCheckedChange(checked: boolean) => void-Fires after every toggle.
classNamestring-Merged onto the track button.

Any other <button> prop is forwarded, including disabled.

Reduced motion

The track and thumb still move to their new state — it's not decorative, it's the control's whole job — just as an instant cut instead of an eased bounce.