Saltar al contenido
ssntl/ui

Switch

Un ajuste de encendido y apagado con una presión rápida y un control que cede un poco mientras se sostiene.

Instalación

$ pnpm dlx shadcn@latest add @ssntl/switch

Uso

tsx
import { Switch, SwitchField } from "@/components/ui/switch"

<SwitchField label="Aceptar pedidos" hint="Los clientes pueden comprar ahora" defaultChecked />
<Switch aria-label="Modo vacaciones" />

Reglas

Lo que este componente espera de ti, tal como se instala en components/ui/switch.md junto al código. El archivo completo agrega cuándo usarlo, teclado, los fundamentos de UI que aplica y cada regla compartida (en inglés).

components/ui/switch.md
## Reglas de Switch

- **Respuesta del interruptor.** La barra se presiona a 0.96 en 150ms, pequeño e inmediato.
- **Compresión y estiramiento.** Mientras se sostiene, el control se ensancha 6% y se aplana 6%, y luego regresa.
- Encendido llena la barra con primary, así el estado se lee sin el movimiento.
- Usa un switch para un ajuste que aplica al instante; usa un Checkbox dentro de un formulario que se envía.
Abrir el archivo de reglas completo (se abre en otra pestaña)

Código

components/ui/switch.tsx
"use client"

import * as React from "react"
import { Switch as SwitchPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

// An on/off setting that applies at once (Radix Switch, role="switch").
// - Toggle feedback: the track presses to 0.96 in 150ms, small and quick.
// - Squash & stretch: while held, the thumb widens 6% and flattens 6%, a
//   hint of physical give; it springs back on release.
// - State never rides on motion alone: on fills the track with primary.
function Switch({
  className,
  ...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
  return (
    <SwitchPrimitive.Root
      data-slot="switch"
      className={cn(
        "group inline-flex h-8 w-13 shrink-0 cursor-pointer items-center rounded-full bg-input p-1",
        "transition-[background-color,scale] duration-(--duration-fast) ease-out active:scale-(--es-press) motion-reduce:active:scale-100",
        "data-[state=checked]:bg-primary",
        "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:cursor-not-allowed disabled:opacity-40",
        className
      )}
      {...props}
    >
      <SwitchPrimitive.Thumb
        data-slot="switch-thumb"
        className={cn(
          "pointer-events-none block size-6 rounded-full bg-card elevation-1",
          "transition-[translate,scale] duration-(--duration-fast) ease-out data-[state=checked]:translate-x-5 rtl:data-[state=checked]:-translate-x-5",
          "group-active:scale-x-106 group-active:scale-y-94 motion-reduce:transition-none motion-reduce:group-active:scale-100"
        )}
      />
    </SwitchPrimitive.Root>
  )
}

/** A full-width row: the label, an optional hint, and the switch at the end. */
function SwitchField({
  label,
  hint,
  id,
  ...props
}: React.ComponentProps<typeof Switch> & { label: string; hint?: string }) {
  const generated = React.useId()
  const controlId = id ?? generated
  return (
    <div
      data-slot="switch-field"
      className="flex min-h-12 items-center justify-between gap-4 py-2"
    >
      <label
        htmlFor={controlId}
        className="grid cursor-pointer gap-0.5 select-none"
      >
        <span className="text-body">{label}</span>
        {hint && (
          <span className="text-caption text-muted-foreground">{hint}</span>
        )}
      </label>
      <Switch id={controlId} {...props} />
    </div>
  )
}

export { Switch, SwitchField }