Toggle

Pronto

Liga/desliga binário com efeito imediato. Diferente de Checkbox: a mudança aplica na hora, sem botão Salvar.

Usar quando

Configurações que aplicam imediatamente: notificações, modo escuro, recursos opcionais, ativar/desativar canal.

Não usar quando

Mudança que requer confirmação (use Checkbox + Salvar). Estado de form que ainda vai ser submetido. Mais de 2 estados.

Variantes

Padrão (label à esquerda)
Receber notificações por e-mail
Modo escuro
Captura automática de andamentos
Com descrição secundária
WhatsApp Business
Recebe e envia mensagens pelo WhatsApp Business API.
2FA por aplicativo
Requer autenticação em dois fatores no login.
Tamanhos: sm e md
Pequeno
Médio (default)

Estados

Off
On
Hover
Focus (Tab)
Disabled (off)
Disabled (on)
Loading (otimista)

Microinterações

MicrointeraçãoDisparada porComportamentoTiming
Track slideclickTrack passa de border (off) para ink (on) com fade200ms cubic-bezier(0.32,0.72,0,1)
Thumb slideclickThumb desliza horizontalmente da esquerda para direita250ms cubic-bezier(0.32,0.72,0,1)
Thumb scalemousedownThumb expande horizontalmente (alongado) durante drag100ms ease
Hover backgroundmouseenterTrack ganha leve glow (border ou ink ficam +5% saturated)150ms ease
Loading spinnerloading=trueSpinner gira no thumb enquanto async600ms linear infinite
Optimistic toggleclickAplica mudança visual antes da confirmação do servidorinstant

Acessibilidade

Acessibilidade — checklist

Teclado
TabMove foco para o toggle
Space / EnterAlterna on/off
ARIA esperado
  • role="switch" // não checkbox
  • aria-checked="true" | "false"
  • aria-label="..." // se sem label visual
  • aria-disabled="true" se disabled
Notas
  • Use role="switch" — semantica diferente de checkbox.
  • A mudança aplica IMEDIATAMENTE — não tem botão Salvar.
  • Em async, use loading state + optimistic update + rollback se falhar.
  • Erros: reverte estado + toast de erro.
  • Toggle NUNCA é usado dentro de form que precisa Submit.

Código

'use client';
import { T, MOTION, transition } from '@/lib/tokens';

interface ToggleProps {
  on: boolean;
  onChange: (on: boolean) => void;
  disabled?: boolean;
  loading?: boolean;
  size?: 'sm' | 'md';
}

export function Toggle({ on, onChange, disabled, loading, size = 'md' }: ToggleProps) {
  const w = size === 'sm' ? 32 : 40;
  const h = size === 'sm' ? 18 : 22;
  const thumb = size === 'sm' ? 14 : 18;
  return (
    <button
      type="button"
      role="switch"
      aria-checked={on}
      aria-disabled={disabled}
      onClick={() => !disabled && onChange(!on)}
      disabled={disabled}
      style={{
        width: w, height: h, borderRadius: 980,
        background: on ? T.ink : T.border,
        border: 'none',
        position: 'relative',
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.5 : 1,
        transition: transition('background-color', 'base'),
        padding: 0,
      }}
    >
      <span style={{
        position: 'absolute',
        top: (h - thumb) / 2,
        left: on ? w - thumb - (h - thumb) / 2 : (h - thumb) / 2,
        width: thumb, height: thumb, borderRadius: 980,
        background: T.surface,
        boxShadow: T.shadowSm,
        transition: transition(['left', 'transform'], 'base'),
      }} />
    </button>
  );
}

Regras

Faça

  • Use role="switch" (não checkbox).
  • Aplica imediatamente — sem botão Salvar.
  • Optimistic update: aplica visual antes do servidor confirmar.
  • Em erro async: reverte + toast.
  • Track preto (T.ink) quando ON, cinza (T.border) quando OFF.
  • Thumb branco com sombra sutil.

Não faça

  • Não use toggle dentro de form com Submit.
  • Não use cor (verde, vermelho) para indicar state — preto/cinza basta.
  • Não exiba labels ON/OFF dentro do track (ruidoso).
  • Não desabilite só visualmente — use disabled real.
  • Não force confirmação para toggle (esse é o ponto: imediato).

Tokens usados

TokenValorPapel
T.ink#0A0A0Atrack quando ON
T.border#E3DFD2track quando OFF
T.surface#FFFFFFthumb
T.shadowSm0 1px 2px rgba(...)sombra do thumb
RADIUS.pill980forma
MOTION.base250msthumb slide