@lattice-ui/switch Stable direction
import Switch
depends on core , motion Switch is the primitive for an on/off toggle: settings flips, feature enables, and any binary control. The root is the toggleable track that owns checked state, and the thumb slides between the two ends as the state changes. You decide whether the primitive animates the track color for you or leaves your own colors untouched.
Reach for Switch when a control is boolean, should toggle on activation, and wants a thumb that animates between off and on positions.
Import
import { Switch } from "@lattice-ui/switch";Anatomy
Root is the toggleable track and is the only required part. Thumb is the sliding handle; include it whenever you want the moving indicator.
Switch anatomy
<Switch.Root> <Switch.Thumb /></Switch.Root>| Part | Required | Responsibility |
|---|---|---|
Switch.Root | yes | The track button: owns checked state, toggles on activation, and animates track color when it owns it. |
Switch.Thumb | no | The handle that animates between the off and on ends of the track. |
Example
A controlled switch with an app-owned track and a custom thumb, letting the primitive animate the track color.
import { useState } from "@rbxts/react";import { Switch } from "@lattice-ui/switch";
export function MusicToggle() { const [enabled, setEnabled] = useState(true);
return ( <Switch.Root checked={enabled} onCheckedChange={setEnabled} trackOnColor={Color3.fromRGB(86, 141, 255)} trackOffColor={Color3.fromRGB(66, 73, 91)} asChild > <frame BackgroundColor3={Color3.fromRGB(66, 73, 91)} BorderSizePixel={0} Size={UDim2.fromOffset(48, 24)} > <uicorner CornerRadius={new UDim(1, 0)} />
<Switch.Thumb> <frame BackgroundColor3={Color3.fromRGB(240, 244, 252)} BorderSizePixel={0} Size={UDim2.fromOffset(20, 20)} > <uicorner CornerRadius={new UDim(1, 0)} /> </frame> </Switch.Thumb> </frame> </Switch.Root> );}By default an asChild switch leaves track color to you ("consumer"), but passing any of trackOnColor/trackOffColor/disabledTrackColor — as above — opts the primitive into animating the color. Set trackColorMode explicitly to remove the ambiguity.
How it behaves
Checked state
Switch.Root is controllable. Pass checked and onCheckedChange to control it, or defaultChecked to run uncontrolled (defaults to false). Activating the root toggles the state; when disabled, activation is ignored and the state cannot change. The default (non-asChild) root renders a textbutton whose Text reflects "On"/"Off".
The thumb
Switch.Thumb animates its Position between the two ends of the track as checked changes, with a small inset on each side. It reads the thumb’s Size (from the slotted child when asChild, otherwise a 16×16 default) to compute the checked-end position, so non-default thumb sizes still land correctly. Pass forceMount to keep the thumb mounted regardless of state.
Track color ownership
trackColorMode decides who writes the track’s BackgroundColor3:
"switch"— the primitive animates betweentrackOnColor,trackOffColor, anddisabledTrackColoras state changes."consumer"— the primitive leaves the track color alone so your slotted/default colors stand.
When trackColorMode is unset, the default is "switch" for a non-asChild root, and "consumer" for an asChild root unless you pass any track color prop (which opts into "switch"). Setting trackColorMode explicitly always wins over that fallback.
Motion
Both the track color (when owned) and the thumb position animate with a short response settle, so toggling feels immediate but smooth rather than snapping instantly.
The root is a Roblox button (textbutton, or your slotted element via asChild). It is made Active and Selectable only while enabled, so a disabled switch drops out of gamepad selection. The thumb is positioned by offset inside the track, so give the track a fixed size; the checked position is derived from the thumb’s own Size and a 2px inset.
API reference
Switch.Root
| Prop | Type | Description |
|---|---|---|
| checked | boolean | Controlled checked state. Pair with onCheckedChange. |
| defaultChecked | boolean | Initial checked state for uncontrolled usage. Defaults to false. |
| onCheckedChange | (checked: boolean) => void | Called when the checked state changes. |
| disabled | boolean | Prevents toggling and removes the switch from selection. Defaults to false. |
| trackColorMode | "consumer" | "switch" | Who owns track BackgroundColor3. Defaults to "switch" without asChild, or "consumer" with asChild unless a track color prop is provided. |
| trackOnColor | Color3 | Track color when checked (when the primitive owns color). Defaults to RGB(86, 141, 255). |
| trackOffColor | Color3 | Track color when unchecked (when the primitive owns color). Defaults to RGB(66, 73, 91). |
| disabledTrackColor | Color3 | Track color when disabled (when the primitive owns color). Defaults to RGB(103, 110, 128). |
| asChild | boolean | Merge the track behavior onto the single child element instead of rendering the default textbutton. |
| children | React.ReactNode | The track contents (typically a Switch.Thumb). Required when asChild is set. |
Switch.Thumb
| Prop | Type | Description |
|---|---|---|
| forceMount | boolean | Keeps the thumb mounted regardless of state. Defaults to false. |
| asChild | boolean | Merge the animated thumb onto the single child element instead of rendering the default frame; the child's Size is used to compute the checked position. |
| children | React.ReactNode | The thumb contents. Required when asChild is set. |