An on/off toggle you can tap, press or drag, with a thumb that stretches under your finger and an optional pending state for saves.
import { Switch } from "@/components/ui/switch"
const settings = [
{
id: "wifi",
title: "Wi-Fi",
description: "Join known networks automatically.",
on: true,
},
{
id: "bluetooth",
title: "Bluetooth",
description: "Connect to headphones and keyboards.",
on: false,
},
{
id: "airdrop",
title: "Nearby sharing",
description: "Let people around you send files.",
on: true,
},
]
export function SwitchDemo() {
return (
<div className="flex w-full max-w-sm flex-col divide-y rounded-xl border">
{settings.map((setting) => (
<label
key={setting.id}
className="flex items-center justify-between gap-4 p-4 text-sm"
>
<span className="flex flex-col gap-0.5">
<span className="font-medium">{setting.title}</span>
<span className="text-muted-foreground">{setting.description}</span>
</span>
<Switch defaultChecked={setting.on} />
</label>
))}
</div>
)
}pnpm dlx shadcn@latest add https://hextaui.com/r/switch.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
- 1
Add the theme tokens to your global CSS, if you haven’t yet.
- 2
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cn - 3
Copy and paste the following code into your project.
components/ui/switch.tsx "use client" import * as React from "react" import { Switch as SwitchPrimitive } from "@base-ui/react/switch" import { IconCheck } from "@tabler/icons-react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "cn" import { Spinner } from "@/components/ui/spinner" import { prefersReducedMotion } from "@/lib/motion" const switchVariants = cva( "group/switch peer relative inline-flex h-(--switch-h) w-(--switch-w) shrink-0 cursor-pointer items-center rounded-full bg-foreground/20 p-0.5 transition-[background-color,box-shadow] duration-200 ease-out-quint outline-none select-none [--switch-dir:1] [--switch-stretch:0px] [--switch-thumb-w:var(--switch-thumb)] [--switch-x:0px] after:absolute after:-inset-1.5 focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-dragging:[--switch-x:var(--switch-drag)] data-invalid:ring-3 data-invalid:ring-destructive/20 data-pending:cursor-progress data-readonly:cursor-default data-[drag=off]:bg-foreground/20 data-[drag=on]:bg-primary data-shake:motion-safe:animate-button-shake rtl:[--switch-dir:-1] dark:bg-foreground/25 dark:data-[drag=off]:bg-foreground/25 dark:data-[drag=on]:bg-primary forced-colors:border pointer-coarse:after:-inset-3 data-checked:bg-primary data-checked:[--switch-x:calc(var(--switch-w)-var(--switch-thumb-w)-4px-var(--switch-stretch))] dark:data-checked:bg-primary forced-colors:data-checked:bg-highlight data-disabled:cursor-not-allowed data-disabled:opacity-50 [&:active:not([data-disabled]):not([data-readonly]):not([data-pending])]:[--switch-stretch:4px]", { variants: { variant: { default: "", ios: "bg-foreground/10 inset-ring-(length:--hairline) inset-ring-foreground/5 [--switch-thumb-w:calc(var(--switch-thumb)*1.5)] data-[drag=off]:bg-foreground/10 data-[drag=on]:bg-success dark:bg-foreground/20 dark:data-[drag=off]:bg-foreground/20 dark:data-[drag=on]:bg-success forced-colors:border data-checked:bg-success data-checked:inset-ring-transparent dark:data-checked:bg-success", }, size: { sm: "[--switch-h:1rem] [--switch-thumb:0.75rem] [--switch-w:1.75rem]", default: "[--switch-h:1.25rem] [--switch-thumb:1rem] [--switch-w:2.25rem]", lg: "[--switch-h:1.5rem] [--switch-thumb:1.25rem] [--switch-w:2.75rem]", }, }, compoundVariants: [ { variant: "ios", size: "sm", className: "[--switch-h:1.5rem] [--switch-thumb:1.25rem] [--switch-w:3.5rem]", }, { variant: "ios", size: "default", className: "[--switch-h:1.75rem] [--switch-thumb:1.5rem] [--switch-w:4rem]", }, { variant: "ios", size: "lg", className: "[--switch-h:2rem] [--switch-thumb:1.75rem] [--switch-w:4.5rem]", }, ], defaultVariants: { variant: "default", size: "default", }, } ) type SwitchProps = Omit<SwitchPrimitive.Root.Props, "onCheckedChange"> & VariantProps<typeof switchVariants> & { icons?: boolean onCheckedChange?: ( checked: boolean, eventDetails: SwitchPrimitive.Root.ChangeEventDetails ) => unknown } function isThenable(value: unknown): value is PromiseLike<unknown> { return ( typeof value === "object" && value !== null && typeof (value as PromiseLike<unknown>).then === "function" ) } function useThumbDrag( rootRef: React.RefObject<HTMLElement | null>, canDrag: () => boolean ) { const drag = React.useRef<{ id: number startX: number from: boolean max: number moved: boolean position: number } | null>(null) const suppressClick = React.useRef(false) const pendingToggle = React.useRef(false) const onPointerDown = (event: React.PointerEvent<HTMLElement>) => { const root = rootRef.current if (!root || event.button !== 0 || !canDrag()) { return } drag.current = { id: event.pointerId, startX: event.clientX, from: root.hasAttribute("data-checked"), max: 0, moved: false, position: 0, } } const onPointerMove = (event: React.PointerEvent<HTMLElement>) => { const state = drag.current const root = rootRef.current if (!state || !root || event.pointerId !== state.id) { return } const rtl = getComputedStyle(root).direction === "rtl" const delta = (event.clientX - state.startX) * (rtl ? -1 : 1) if (!state.moved && Math.abs(delta) < 3) { return } if (!state.moved) { const thumb = root.querySelector<HTMLElement>("[data-slot=switch-thumb]") state.max = Math.max(0, root.clientWidth - (thumb?.offsetWidth ?? 0) - 4) state.moved = true root.setPointerCapture?.(event.pointerId) root.setAttribute("data-dragging", "") } const base = state.from ? state.max : 0 state.position = Math.min(state.max, Math.max(0, base + delta)) root.setAttribute( "data-drag", state.position > state.max / 2 ? "on" : "off" ) root.style.setProperty("--switch-drag", `${state.position}px`) } const finish = (event: React.PointerEvent<HTMLElement>) => { const state = drag.current const root = rootRef.current drag.current = null if (!state || !root || !state.moved) { return } root.removeAttribute("data-dragging") root.removeAttribute("data-drag") root.style.removeProperty("--switch-drag") if (root.hasPointerCapture?.(event.pointerId)) { root.releasePointerCapture(event.pointerId) } const to = state.position > state.max / 2 suppressClick.current = true pendingToggle.current = to !== state.from && event.type === "pointerup" setTimeout(() => { suppressClick.current = false if (pendingToggle.current) { pendingToggle.current = false root.click() } }, 0) } const onClickCapture = (event: React.MouseEvent<HTMLElement>) => { if (suppressClick.current) { event.preventDefault() event.stopPropagation() } } return { onPointerDown, onPointerMove, onPointerUp: finish, onPointerCancel: finish, onClickCapture, } } function Switch({ className, size = "default", variant = "default", icons = false, checked: checkedProp, defaultChecked = false, onCheckedChange, disabled, readOnly, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onClickCapture, onAnimationEnd, ref, ...props }: SwitchProps) { const rootRef = React.useRef<HTMLElement | null>(null) const [internal, setInternal] = React.useState(defaultChecked) const [pending, setPending] = React.useState(false) const controlled = checkedProp !== undefined const checked = controlled ? checkedProp : internal const latest = React.useRef(0) const blocked = Boolean(disabled || readOnly || pending) const dragHandlers = useThumbDrag(rootRef, () => !blocked) const setRef = React.useCallback( (node: HTMLElement | null) => { rootRef.current = node if (typeof ref === "function") { return ref(node) } if (ref) { ref.current = node } return undefined }, [ref] ) const handleChange = ( next: boolean, details: SwitchPrimitive.Root.ChangeEventDetails ) => { if (pending) { details.cancel() return } if (!controlled) { setInternal(next) } const result = onCheckedChange?.(next, details) if (!isThenable(result)) { return } const id = ++latest.current setPending(true) Promise.resolve(result).then( () => { if (latest.current === id) { setPending(false) } }, () => { if (latest.current !== id) { return } setPending(false) if (!controlled) { setInternal(!next) } const root = rootRef.current if (root && !prefersReducedMotion()) { root.removeAttribute("data-shake") void root.offsetWidth root.setAttribute("data-shake", "") } } ) } return ( <SwitchPrimitive.Root ref={setRef} checked={checked} onCheckedChange={handleChange} disabled={disabled} readOnly={readOnly} aria-busy={pending || undefined} data-pending={pending ? "" : undefined} data-size={size} data-variant={variant} className={(state) => cn( switchVariants({ variant, size }), typeof className === "function" ? className(state) : className ) } onPointerDown={(event) => { onPointerDown?.(event) if (!event.defaultPrevented) { dragHandlers.onPointerDown(event) } }} onPointerMove={(event) => { onPointerMove?.(event) dragHandlers.onPointerMove(event) }} onPointerUp={(event) => { onPointerUp?.(event) dragHandlers.onPointerUp(event) }} onPointerCancel={(event) => { onPointerCancel?.(event) dragHandlers.onPointerCancel(event) }} onClickCapture={(event) => { onClickCapture?.(event) dragHandlers.onClickCapture(event) }} onAnimationEnd={(event) => { onAnimationEnd?.(event) if (event.target === event.currentTarget) { event.currentTarget.removeAttribute("data-shake") } }} {...props} data-slot="switch" > {icons ? ( <span aria-hidden="true" data-slot="switch-icons" className="pointer-events-none absolute inset-0 flex items-center justify-between px-[calc((var(--switch-h)-0.625rem)/2)] text-primary-foreground group-data-[variant=ios]/switch:text-success-foreground [&_svg]:size-2.5" > <IconCheck stroke={3} className="opacity-0 transition-opacity duration-200 group-data-[drag=off]/switch:opacity-0 group-data-[drag=on]/switch:opacity-100 group-data-checked/switch:opacity-100" /> <span className="size-2 rounded-full opacity-100 inset-ring-[1.5px] inset-ring-muted-foreground transition-opacity duration-200 group-data-[drag=off]/switch:opacity-100 group-data-[drag=on]/switch:opacity-0 group-data-checked/switch:opacity-0" /> </span> ) : null} <SwitchPrimitive.Thumb data-slot="switch-thumb" className="pointer-events-none relative z-1 flex h-(--switch-thumb) w-[calc(var(--switch-thumb-w)+var(--switch-stretch))] translate-x-[calc(var(--switch-x)*var(--switch-dir))] items-center justify-center rounded-full bg-background ring-(length:--hairline) ring-foreground/5 transition-[translate,width] duration-300 ease-spring group-data-dragging/switch:transition-none motion-reduce:transition-none dark:bg-foreground dark:group-data-[drag=off]/switch:bg-foreground dark:group-data-[drag=on]/switch:bg-primary-foreground dark:group-data-[variant=ios]/switch:group-data-[drag=on]/switch:bg-foreground dark:group-data-checked/switch:bg-primary-foreground dark:group-data-[variant=ios]/switch:group-data-checked/switch:bg-foreground forced-colors:border forced-colors:bg-canvas-text" > {pending ? ( <Spinner aria-hidden size={null} className="size-[calc(var(--switch-thumb)-0.375rem)] text-muted-foreground" /> ) : null} </SwitchPrimitive.Thumb> </SwitchPrimitive.Root> ) } export { Switch, switchVariants } export type { SwitchProps }components/ui/spinner.tsx "use client" import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "cn" import { useDelayedLoading, type DelayedLoadingOptions, } from "@/hooks/use-delayed-loading" const spinnerVariants = cva("shrink-0", { variants: { size: { sm: "size-3.5", default: "size-4", lg: "size-5", xl: "size-8", }, }, defaultVariants: { size: "default", }, }) type SpinnerVariant = "ticks" | "ring" type SpinnerProps = Omit<React.ComponentProps<"svg">, "children"> & VariantProps<typeof spinnerVariants> & { variant?: SpinnerVariant label?: string animated?: boolean loading?: boolean delay?: number minDuration?: number } const ticks = Array.from({ length: 8 }, (_, index) => index) function Spinner({ className, variant = "ticks", size = "default", label = "Loading", animated = true, loading, delay, minDuration, ...props }: SpinnerProps) { const delayed = useDelayedLoading(loading ?? false, { delay, minDuration }) const shown = loading === undefined || delayed if (!shown) { return null } const hidden = props["aria-hidden"] === true || props["aria-hidden"] === "true" return ( <svg viewBox="0 0 24 24" fill="none" role={hidden ? undefined : "status"} aria-label={hidden ? undefined : label} data-slot="spinner" data-variant={variant} className={cn( spinnerVariants({ size }), animated && (variant === "ticks" ? "motion-safe:animate-spinner-ticks" : "motion-safe:animate-spinner-rotate"), animated && "motion-reduce:animate-spinner-pulse", loading !== undefined && "transition-opacity duration-200 ease-out motion-reduce:transition-none starting:opacity-0", className )} {...props} > {variant === "ticks" ? ( ticks.map((index) => ( <line key={index} x1="12" y1="2.75" x2="12" y2="6.75" stroke="currentColor" strokeWidth="2.25" strokeLinecap="round" opacity={1 - index * 0.11} transform={`rotate(${-index * 45} 12 12)`} /> )) ) : ( <> <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.5" opacity="0.2" /> <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeDasharray="16 60" transform="rotate(-90 12 12)" className={cn(animated && "motion-safe:animate-spinner-dash")} /> </> )} </svg> ) } export { Spinner, spinnerVariants, useDelayedLoading } export type { DelayedLoadingOptions, SpinnerProps, SpinnerVariant }lib/motion.ts import * as React from "react" const easeOut = "cubic-bezier(0.23, 1, 0.32, 1)" const easeInOut = "cubic-bezier(0.77, 0, 0.175, 1)" const easeSpring = "linear(0, 0.015 2%, 0.0532 4%, 0.1065 6%, 0.1686 8%, 0.2351 10%, 0.3363 13%, 0.4332 16%, 0.5495 20%, 0.648 24%, 0.7287 28%, 0.807 33%, 0.8646 38%, 0.9128 44%, 0.9447 50%, 0.968 57%, 0.9834 65%, 0.9924 74%, 0.9975 85%, 1)" const duration = { press: 100, release: 200, hover: 150, enter: 200, exit: 150, morph: 300, } as const function prefersReducedMotion() { return ( typeof window === "undefined" || typeof window.matchMedia !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches ) } type SizeAxis = "width" | "height" type SizeMorphOptions = { axis: SizeAxis enabled?: boolean duration?: number easing?: string } function readSize(element: HTMLElement, axis: SizeAxis) { const value = parseFloat(getComputedStyle(element)[axis]) if (Number.isFinite(value)) { return value } const rect = element.getBoundingClientRect() return axis === "width" ? rect.width : rect.height } function attachSizeMorph( element: HTMLElement, axis: SizeAxis, time: number, easing: string ) { if ( typeof MutationObserver === "undefined" || typeof element.animate !== "function" ) { return undefined } let settled = readSize(element, axis) let animation: Animation | null = null const morph = () => { const from = animation ? readSize(element, axis) : settled animation?.cancel() animation = null const to = readSize(element, axis) settled = to if ( Math.abs(from - to) < 0.5 || prefersReducedMotion() || !element.isConnected || element.getClientRects().length === 0 ) { element.removeAttribute("data-morphing") return } element.setAttribute("data-morphing", "") const running = element.animate( [{ [axis]: `${from}px` }, { [axis]: `${to}px` }], { duration: time, easing } ) animation = running running.onfinish = () => { if (animation === running) { animation = null element.removeAttribute("data-morphing") settled = readSize(element, axis) } } } const mutations = new MutationObserver(morph) mutations.observe(element, { childList: true, subtree: true, characterData: true, }) const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => { if (!animation) { settled = readSize(element, axis) } }) resize?.observe(element) return () => { mutations.disconnect() resize?.disconnect() animation?.cancel() element.removeAttribute("data-morphing") } } function useSizeMorph<T extends HTMLElement>({ axis, enabled = true, duration: time = duration.morph, easing = easeOut, }: SizeMorphOptions): React.RefCallback<T> { return React.useCallback( (element: T | null) => { if (!element || !enabled) { return undefined } return attachSizeMorph(element, axis, time, easing) }, [axis, enabled, time, easing] ) } function useSlidingHighlight( barRef: React.RefObject<HTMLElement | null>, highlightRef: React.RefObject<HTMLElement | null>, selector: string, attribute = "data-popup-open" ) { React.useLayoutEffect(() => { const bar = barRef.current const highlight = highlightRef.current if (!bar || !highlight) { return } let current: HTMLElement | null = null const place = (trigger: HTMLElement, instant: boolean) => { if (instant || prefersReducedMotion()) { highlight.setAttribute("data-instant", "") } else { highlight.removeAttribute("data-instant") } const frame = bar.getBoundingClientRect() const box = trigger.getBoundingClientRect() const scale = bar.offsetWidth > 0 ? frame.width / bar.offsetWidth : 1 highlight.style.left = "0px" highlight.style.width = `${box.width / scale}px` highlight.style.height = `${box.height / scale}px` highlight.style.transform = `translate(${(box.left - frame.left) / scale - bar.clientLeft}px, ${(box.top - frame.top) / scale - bar.clientTop}px)` } const sync = () => { const trigger = bar.querySelector<HTMLElement>(selector) if (trigger === current) { if (trigger) { place(trigger, true) } return } const appearing = current === null current = trigger if (!trigger) { highlight.removeAttribute("data-visible") return } place(trigger, appearing) if (appearing) { void highlight.offsetWidth } highlight.setAttribute("data-visible", "") } sync() const mutations = new MutationObserver(sync) mutations.observe(bar, { subtree: true, childList: true, attributes: true, attributeFilter: [attribute], }) const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => { if (current) { place(current, true) } }) resize?.observe(bar) const onScroll = () => { if (current) { place(current, true) } } bar.addEventListener("scroll", onScroll, { capture: true, passive: true }) return () => { mutations.disconnect() resize?.disconnect() bar.removeEventListener("scroll", onScroll, { capture: true }) } }, [barRef, highlightRef, selector, attribute]) } export { duration, easeInOut, easeOut, easeSpring, prefersReducedMotion, useSizeMorph, useSlidingHighlight, } export type { SizeMorphOptions } - 4
Update the import paths to match your project setup.
import { Switch } from "@/components/ui/switch"<label className="flex items-center gap-3">
<Switch defaultChecked />
Wi-Fi
</label>Press and the thumb stretches toward the other side before springing across, so you feel the press land. You can also grab the thumb and drag it: past the middle it flips, short of it it snaps back.
iOS#
variant="ios" uses a taller track, a capsule thumb and green when on. Press, drag and saving work the same.
import { Switch } from "@/components/ui/switch"
export function SwitchIos() {
return (
<div className="flex flex-col gap-4 text-sm">
<label className="flex items-center gap-3">
<Switch variant="ios" />
Airplane mode
</label>
<label className="flex items-center gap-3">
<Switch variant="ios" defaultChecked />
Wi-Fi
</label>
<div className="flex items-center gap-4">
<Switch variant="ios" size="sm" aria-label="Small" defaultChecked />
<Switch variant="ios" size="lg" aria-label="Large" defaultChecked />
</div>
</div>
)
}Saving#
Return a promise from onCheckedChange and the switch flips right away with a spinner in the thumb. If it rejects, the switch flips back and shakes.
"use client"
import { Switch } from "@/components/ui/switch"
function save(succeed: boolean) {
return new Promise<void>((resolve, reject) =>
setTimeout(() => (succeed ? resolve() : reject(new Error("Offline"))), 1200)
)
}
export function SwitchAsync() {
return (
<div className="flex flex-col gap-4 text-sm">
<label className="flex items-center gap-3">
<Switch onCheckedChange={() => save(true)} />
Sync to cloud (saves)
</label>
<label className="flex items-center gap-3">
<Switch defaultChecked onCheckedChange={() => save(false)} />
Public profile (fails and flips back)
</label>
</div>
)
}On and off marks#
icons adds a check and a ring to the track, so the state doesn't rely on color.
import { Switch } from "@/components/ui/switch"
export function SwitchIcons() {
return (
<div className="flex flex-col gap-4 text-sm">
<label className="flex items-center gap-3">
<Switch icons defaultChecked />
Captions
</label>
<label className="flex items-center gap-3">
<Switch icons size="lg" />
Reduce transparency
</label>
</div>
)
}Sizes#
sm, default and lg.
import { Switch } from "@/components/ui/switch"
export function SwitchSizes() {
return (
<div className="flex items-center gap-6">
<Switch size="sm" aria-label="Small" defaultChecked />
<Switch aria-label="Default" defaultChecked />
<Switch size="lg" aria-label="Large" defaultChecked />
</div>
)
}In a field#
With Field, the label and description are linked to the switch.
Product news and offers, about once a month.
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
export function SwitchField() {
return (
<Field orientation="horizontal" className="w-full max-w-sm">
<FieldContent>
<FieldLabel>Marketing emails</FieldLabel>
<FieldDescription>
Product news and offers, about once a month.
</FieldDescription>
</FieldContent>
<Switch name="marketing" />
</Field>
)
}Disabled and read-only#
readOnly keeps the value visible and focusable but won't change it.
import { Switch } from "@/components/ui/switch"
export function SwitchStates() {
return (
<div className="flex flex-col gap-4 text-sm">
<label className="flex items-center gap-3">
<Switch disabled />
Disabled
</label>
<label className="flex items-center gap-3">
<Switch disabled defaultChecked />
Disabled and on
</label>
<label className="flex items-center gap-3">
<Switch readOnly defaultChecked />
Read-only (managed by your admin)
</label>
</div>
)
}Controlled#
Pass checked and onCheckedChange.
"use client"
import * as React from "react"
import { Switch } from "@/components/ui/switch"
export function SwitchControlled() {
const [dark, setDark] = React.useState(false)
return (
<label className="flex items-center gap-3 text-sm">
<Switch checked={dark} onCheckedChange={setDark} />
Dark mode is {dark ? "on" : "off"}
</label>
)
}Right to left#
The thumb starts on the right and slides left, and dragging follows.
import { Switch } from "@/components/ui/switch"
export function SwitchRtl() {
return (
<div dir="rtl" className="flex flex-col gap-4 text-sm">
<label className="flex items-center gap-3">
<Switch defaultChecked icons />
الإشعارات
</label>
<label className="flex items-center gap-3">
<Switch />
الوضع الداكن
</label>
</div>
)
}| Key | Action |
|---|---|
| SpaceEnter | Turns the focused switch on or off. |
- The switch is a
switchrole named by its label. Wrap it in a<label>or useField. - While saving, it's
aria-busyand ignores new presses. - The hit area is larger than the switch, and larger again on touch.
- With reduced motion, the thumb moves instantly and nothing shakes.
| Prop | Type | Default |
|---|---|---|
checked | boolean | – |
defaultChecked | boolean | false |
onCheckedChangeReturn a promise to show the saving state. On rejection an uncontrolled switch flips back. | (checked, details) => void | Promise | – |
variant | "default" | "ios" | "default" |
size | "sm" | "default" | "lg" | "default" |
iconsShow on and off marks in the track. | boolean | false |
name | string | – |
value | string | – |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
render | ReactElement | (props, state) => ReactElement | <span> |
| Attribute | Description |
|---|---|
data-slot="switch" | The track, with data-size and data-variant. |
data-slot="switch-thumb" | The thumb. |
data-checked / data-unchecked | Whether the switch is on. |
data-dragging | Present while the thumb is being dragged. |
data-pending | Present while a returned promise is pending. |
--switch-w / --switch-h / --switch-thumb / --switch-thumb-w | Track and thumb sizes, set by size. |