Switch
Włącza lub wyłącza ustawienie — zmiana działa w chwili przełączenia.
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
- 1TorPo włączeniu wypełniony kolorem marki, po wyłączeniu neutralny. Niewidoczny obszar klikalny powiększa go do 44 × 36 px.
- 2UchwytPo przełączeniu przesuwa się na drugą stronę ruchem sprężyny, a podczas wciśnięcia się rozciąga.
- 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.jsonUż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.
"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>
)
}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.
Pozwól recenzentom komentować wdrożenia podglądowe.
Klienci pozostają przy załadowanej wersji do czasu odświeżenia strony.
Każdy, kto ma link, może przeczytać wynik budowania.
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
Powiadomienia są włączone — kliknij, aby wyłączyć
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.
| Klawisz | Dział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-motionuchwyt 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.