Adaptive Switch
ericts-ui/adaptive-switchFreeComponent
An accessible switch with optional state labels, three sizes, and configurable motion.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/adaptive-switch.jsonSource
1"use client";23import { Switch as SwitchPrimitive } from "@base-ui/react/switch";4import {5 motion,6 useReducedMotion,7 type MotionStyle,8 type Transition,9 type Variants,10} from "motion/react";11import { useState, type ReactNode } from "react";1213import { cn } from "@/lib/utils";1415export type AdaptiveSwitchSize = "sm" | "default" | "lg";16export type AdaptiveSwitchAnimation = "elastic" | "smooth" | "none";1718export type AdaptiveSwitchProps = Omit<19 SwitchPrimitive.Root.Props,20 "children"21> & {22 /** Text revealed on the left when the switch is checked. */23 checkedLabel?: ReactNode;24 /** Text revealed on the right when the switch is unchecked. */25 uncheckedLabel?: ReactNode;26 /** Controls the track and thumb dimensions. */27 size?: AdaptiveSwitchSize;28 /**29 * Controls the thumb motion. Defaults to elastic when labels are present and30 * smooth for a conventional switch.31 */32 animation?: AdaptiveSwitchAnimation;33};3435const ELASTIC_SETTLE_TRANSITION: Transition = {36 duration: 0.52,37 times: [0, 0.09, 0.2, 0.31, 0.4, 0.53, 0.64, 0.74, 0.82, 0.89, 0.95, 1],38 ease: [0.65, 0, 0.35, 1],39};4041const REDUCED_TRANSITION: Transition = { duration: 0 };42const SLIDE_TRANSITION: Transition = {43 duration: 0.14,44 ease: [0.22, 1, 0.36, 1],45};46const ELASTIC_SCALE_X = [47 1.16, 1.5, 1.24, 0.9, 1.06, 0.97, 1.02, 0.992, 1.012, 0.996, 1,48] as const;49const ELASTIC_SCALE_Y = ELASTIC_SCALE_X.map((scaleX) => 1 / scaleX);5051function getStaticThumbStyle(52 checked: boolean,53 hasLabels: boolean,54): MotionStyle {55 return {56 x: checked ? (hasLabels ? "100%" : "calc(100% - 2px)") : "0%",57 scaleX: 1,58 scaleY: 1,59 originX: checked ? 1 : 0,60 originY: 0.5,61 };62}6364function createThumbVariants(65 hasLabels: boolean,66 animation: AdaptiveSwitchAnimation,67 shouldReduceMotion: boolean,68): Variants {69 const checkedX = hasLabels ? "100%" : "calc(100% - 2px)";70 const checkedWarmupX = hasLabels ? "24%" : "calc(24% - 2px)";71 const checkedStretchX = hasLabels ? "50%" : "calc(50% - 2px)";72 const checkedSettleX = hasLabels ? "76%" : "calc(76% - 2px)";7374 if (shouldReduceMotion || animation !== "elastic") {75 const transition =76 shouldReduceMotion || animation === "none"77 ? REDUCED_TRANSITION78 : SLIDE_TRANSITION;7980 return {81 checked: {82 x: checkedX,83 scaleX: 1,84 scaleY: 1,85 originX: 1,86 originY: 0.5,87 transition,88 },89 unchecked: {90 x: "0%",91 scaleX: 1,92 scaleY: 1,93// … truncated
What it pulls in
npm packages
Files it writes
More from ericts-ui
All 43 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Markcheck-mark | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-dialog | ericts-ui | Components | Free | 1 dep | |
| Expandable Panelexpandable-panel | ericts-ui | Components | Free | 2 deps | |
| Expandable Segmented Tabsexpandable-segmented-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Sliderexpandable-slider | ericts-ui | Components | Free | no deps |
