Przejdź do treści

Switch

Włącza lub wyłącza ustawienie — zmiana działa w chwili przełączenia.

Kod źródłowyBase UIRejestr
import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"

export default function SwitchDemo() {
  return (
    <Field orientation="horizontal" className="w-60">
      <Switch id="auto-deploy" defaultChecked />
      <FieldLabel htmlFor="auto-deploy">Wdrażaj po każdym pushu</FieldLabel>
    </Field>
  )
}

Anatomia

  1. 1TorPo włączeniu wypełniony kolorem marki, po wyłączeniu neutralny. Niewidoczny obszar klikalny powiększa go do 44 × 36 px.
  2. 2UchwytPo przełączeniu przesuwa się na drugą stronę ruchem sprężyny, a podczas wciśnięcia się rozciąga.
  3. 3EtykietaNazywa ustawienie, a nie czynność. Kliknięcie etykiety również zmienia stan przełącznika.

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/switch.json

Użycie

import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
<Field orientation="horizontal">
  <Switch id="auto-deploy" />
  <FieldLabel htmlFor="auto-deploy">Deploy on every push</FieldLabel>
</Field>

Uchwyt porusza się ruchem sprężyny i rozciąga się pod naciskiem — tak jak fizyczny przełącznik ustępuje pod palcem. Przy ograniczonym ruchu po prostu przeskakuje na miejsce.

Przykłady

Przełącznik czy pole wyboru?

Przełącznik działa jak włącznik światła: jego przestawienie od razu coś zmienia — bez przycisku Zapisz i bez potwierdzenia. Pole wyboru to odpowiedź w formularzu: nic się nie dzieje, dopóki formularz nie zostanie wysłany. Tutaj przełącznik zmienia wygląd karty w chwili przestawienia.

Tryb nocnyWłącza się o 22:00
Aktywny
"use client"

import * as React from "react"
import { MoonIcon } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
import { cn } from "@/lib/utils"

export default function SwitchImmediate() {
  const [dark, setDark] = React.useState(false)

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <Field orientation="horizontal">
        <Switch id="preview-dark" checked={dark} onCheckedChange={setDark} />
        <FieldLabel htmlFor="preview-dark">Ciemny podgląd</FieldLabel>
      </Field>
      {/* The switch applies instantly — no Save button, no confirmation. */}
      <div
        className={cn(
          dark ? "dark" : "light",
          "flex items-center justify-between gap-4 rounded-xl border bg-card p-4 text-card-foreground shadow-raised transition-colors duration-base"
        )}
      >
        <div className="flex items-center gap-3">
          <span className="flex size-9 items-center justify-center rounded-lg bg-brand-3 text-brand-11">
            <MoonIcon className="size-4" />
          </span>
          <div className="flex flex-col">
            <span className="text-sm font-medium">Tryb nocny</span>
            <span className="text-xs text-muted-foreground">
              Włącza się o 22:00
            </span>
          </div>
        </div>
        <div className="flex items-center gap-2">
          <Badge variant="success">Aktywny</Badge>
          <Button size="xs" variant="outline">
            Edytuj
          </Button>
        </div>
      </div>
    </div>
  )
}
Prosta zasada
Jeśli zmiana zaczyna działać dopiero po kliknięciu Zapisz lub Wyślij, użyj komponentu Checkbox. Jeśli ludzie spodziewają się, że zobaczą efekt od razu, użyj przełącznika.

Lista ustawień

Najczęstsze miejsce dla przełączników: jedno ustawienie w wierszu, etykieta i krótki opis po lewej, przełącznik po prawej — tam, gdzie wzrok trafia po przeczytaniu. Separatory ułatwiają przeglądanie długich list.

import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldSeparator,
} from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"

const settings = [
  {
    id: "preview-comments",
    title: "Komentarze w podglądach",
    description: "Pozwól recenzentom komentować wdrożenia podglądowe.",
    checked: true,
  },
  {
    id: "skew-protection",
    title: "Ochrona przed rozbieżnością wersji",
    description: "Klienci pozostają przy załadowanej wersji do czasu odświeżenia strony.",
    checked: true,
  },
  {
    id: "public-logs",
    title: "Publiczne logi budowania",
    description: "Każdy, kto ma link, może przeczytać wynik budowania.",
    checked: false,
  },
]

export default function SwitchSettings() {
  return (
    <FieldGroup className="w-full max-w-md gap-4 rounded-xl border bg-card p-5 shadow-raised">
      {settings.map((setting, index) => (
        <div key={setting.id} className="flex flex-col gap-4">
          {index > 0 && <FieldSeparator />}
          <Field orientation="horizontal">
            <FieldContent>
              <FieldLabel htmlFor={setting.id}>{setting.title}</FieldLabel>
              <FieldDescription>{setting.description}</FieldDescription>
            </FieldContent>
            <Switch id={setting.id} defaultChecked={setting.checked} />
          </Field>
        </div>
      ))}
    </FieldGroup>
  )
}

Rozmiary

size="sm" pasuje do gęstych tabel i kompaktowych paneli; domyślny rozmiar sprawdza się w formularzach i na stronach ustawień.

import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"

export default function SwitchSizes() {
  return (
    <div className="flex w-80 flex-col gap-4">
      <Field orientation="horizontal">
        <Switch id="size-sm" size="sm" defaultChecked />
        <FieldLabel htmlFor="size-sm" className="text-body-xs">
          Mały — gęste listy i wiersze tabel
        </FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <Switch id="size-default" defaultChecked />
        <FieldLabel htmlFor="size-default" className="font-normal">
          Domyślny — ustawienia i formularze
        </FieldLabel>
      </Field>
    </div>
  )
}

Stany

Wyłączone przełączniki zachowują swoje położenie, więc nadal widać bieżącą wartość. Gdy ustawienie jest wymagane, oznacz przełącznik atrybutem aria-invalid i wyjaśnij powód w FieldError.

import {
  Field,
  FieldContent,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"

export default function SwitchStates() {
  return (
    <FieldGroup className="w-80 gap-4">
      <Field orientation="horizontal" data-disabled>
        <Switch id="switch-disabled-off" disabled />
        <FieldLabel htmlFor="switch-disabled-off" className="font-normal">
          Zablokowany
        </FieldLabel>
      </Field>
      <Field orientation="horizontal" data-disabled>
        <Switch id="switch-disabled-on" disabled defaultChecked />
        <FieldLabel htmlFor="switch-disabled-on" className="font-normal">
          Zablokowany i włączony
        </FieldLabel>
      </Field>
      <Field orientation="horizontal" data-invalid>
        <Switch id="switch-invalid" aria-invalid />
        <FieldContent>
          <FieldLabel htmlFor="switch-invalid">
            Uwierzytelnianie dwuskładnikowe
          </FieldLabel>
          <FieldError>
            Twoja organizacja wymaga 2FA od wszystkich członków.
          </FieldError>
        </FieldContent>
      </Field>
    </FieldGroup>
  )
}

Wytyczne

Kiedy używać

  • Do ustawień, które działają natychmiast: powiadomień, integracji, włączania funkcji, trybu ciemnego.
  • Do stanów binarnych, które ludzie rozumieją bez dodatkowych wyjaśnień — włączone albo wyłączone.

Kiedy nie używać

  • W formularzach, które się wysyła — użyj komponentu Checkbox.
  • Do wyborów innych niż włącz/wyłącz, takich jak Miesięcznie czy Rocznie — użyj komponentu Radio Group lub Toggle Group.
  • Do akcji z konsekwencjami, które wymagają potwierdzenia — użyj komponentów Button i Alert Dialog.

Etykiety

Nazywaj ustawienie, a nie stan. Przełącznik sam pokazuje włączone i wyłączone, więc etykieta powinna mówić, co jest włączane — nigdy Włącz / Wyłącz i nigdy tekst, który zmienia się po przełączeniu.

Powiadomienia e-mail

DobrzeEtykieta nazywa ustawienie, a przełącznik pokazuje jego stan.

Powiadomienia są włączone — kliknij, aby wyłączyć

ŹleEtykieta, która zmienia się razem z wartością, sprawia, że stanu nie da się odczytać.

Informacja zwrotna

Przełącznik działa natychmiast, więc potwierdzaj wynik, gdy nie widać go na ekranie — na przykład powiadomieniem Toast o treści Włączono komentarze w podglądach — a jeśli żądanie się nie powiedzie, cofnij zmianę (i wyjaśnij dlaczego).

Dostępność

Przełącznik renderuje role="switch" z aria-checked oraz ukryte pole typu checkbox na potrzeby formularzy. Sąsiedni FieldLabel z htmlFor jednocześnie nadaje mu nazwę i przełącza go po kliknięciu.

KlawiszDziałanie
Tab
Przenosi fokus na przełącznik. Fokus widać jako pierścień 2 px odsunięty od toru.
SpacjaEnter
Zmienia stan przełącznika.
  • Krawędzie. Tor przełącznika w pozycji „wyłączone” używa tokenu input, który ma kontrast co najmniej 3:1 względem strony (WCAG 1.4.11); w pozycji „włączone” ma pełne wypełnienie w kolorze marki. Stan przekazuje położenie uchwytu — nie sam kolor.
  • Rozmiar celu. Tor ma 36 × 20 px, a przezroczysty obszar klikalny powiększa go do 44 × 36 px.
  • Ruch. Przy prefers-reduced-motion uchwyt zmienia położenie i szerokość natychmiast, bez ruchu sprężyny.

Dokumentacja API

Switch

Renderuje <span role="switch"> z uchwytem i ukrytym <input type="checkbox">. Przyjmuje wszystkie propsy Switch.Root z Base UI.

PropTypDomyślnie
size

Rozmiar toru: 28 × 16 px lub 36 × 20 px.

"sm" | "default""default"
checked

Czy przełącznik jest włączony. W trybie kontrolowanym używaj razem z onCheckedChange.

booleanBrak wartości domyślnej
defaultChecked

Stan początkowy w trybie niekontrolowanym.

booleanfalse
onCheckedChange

Wywoływana po przełączeniu — tu zastosuj ustawienie.

(checked: boolean, details) => voidBrak wartości domyślnej
name

Wysyła stan przełącznika z formularzem.

stringBrak wartości domyślnej
value

Wartość wysyłana, gdy przełącznik jest włączony.

stringBrak wartości domyślnej
uncheckedValue

Wartość wysyłana, gdy przełącznik jest wyłączony.

stringBrak wartości domyślnej
required

Wymaga włączenia przełącznika przed wysłaniem formularza.

booleanfalse
disabled

Ignoruje interakcję i przygasza kontrolkę.

booleanfalse
readOnly

Pokazuje stan, ale nie pozwala go zmienić.

booleanfalse
aria-invalid

Pokazuje stan błędu na torze przełącznika w pozycji „wyłączone”.

booleanBrak wartości domyślnej