A Radix Switch whose thumb slides with a spring-approximation ease and stretches subtly toward the travel direction while pressed; color changes instantly.
npx shadcn@latest add @paragon/switch"use client";
import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
export interface SwitchProps
extends React.ComponentProps<typeof SwitchPrimitive.Root> {
/** Disables the pressed-thumb stretch where the motion would distract. */
static?: boolean;
}
/**
* Radix Switch. The thumb slides with a zero-bounce spring approximation and
* stretches ~2px toward the travel direction while pressed (via scaleX with a
* state-dependent transform-origin, so it stays compositor-only). Track color
* changes instantly — color is state, not motion.
*/
export function Switch({
className,
static: isStatic = false,
...props
}: SwitchProps) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"group inline-flex h-5 w-9 shrink-0 items-center rounded-full bg-input",
"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"data-[state=checked]:bg-primary",
"disabled:cursor-not-allowed disabled:opacity-50",
// Extend the hit area to 40px tall.
"relative after:absolute after:top-1/2 after:left-1/2 after:h-10 after:w-full after:min-w-10 after:-translate-x-1/2 after:-translate-y-1/2",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
className={cn(
"pointer-events-none block size-4 translate-x-0.5 rounded-full bg-background shadow-sm",
"transition-[translate,scale] duration-200 [transition-timing-function:var(--ease-spring)] motion-reduce:transition-none",
"data-[state=checked]:translate-x-[18px]",
// Stretch anchors on the trailing edge so growth points toward travel.
"origin-left data-[state=checked]:origin-right",
"dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground",
!isStatic && "group-active:scale-x-[1.125]",
)}
/>
</SwitchPrimitive.Root>
);
}