Checkbox
Włącza lub wyłącza pojedynczą opcję albo zaznacza dowolną liczbę opcji z zestawu — zmiana obowiązuje po wysłaniu formularza.
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
export default function CheckboxDemo() {
return (
<FieldGroup className="w-64 gap-3">
<Field orientation="horizontal">
<Checkbox id="demo-mentions" defaultChecked />
<FieldLabel htmlFor="demo-mentions" className="font-normal">
Wzmianki i odpowiedzi
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="demo-assigned" defaultChecked />
<FieldLabel htmlFor="demo-assigned" className="font-normal">
Zgłoszenia przypisane do mnie
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="demo-digest" />
<FieldLabel htmlFor="demo-digest" className="font-normal">
Cotygodniowe podsumowanie
</FieldLabel>
</Field>
</FieldGroup>
)
}Anatomia
Odpowiedzi i wzmianki na bieżąco.
- 1PoleKwadrat 16 px z niewidocznym obszarem kliknięcia 24 px. Zaznaczony wypełnia się kolorem marki i rysuje znacznik jak pociągnięcie pióra; w stanie pośrednim pokazuje kreskę.
- 2OpisOpcjonalne wyjaśnienie, co robi opcja — wyciszonym tekstem.
- 3EtykietaNazywa opcję. Kliknięcie etykiety przełącza pole.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/checkbox.jsonUżycie
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldLabel } from "@/components/ui/field"<Field orientation="horizontal">
<Checkbox id="terms" />
<FieldLabel htmlFor="terms">Accept terms and conditions</FieldLabel>
</Field>Po zaznaczeniu pola znacznik rysuje się sam, jak pociągnięcie pióra. Pola zaznaczone i w stanie pośrednim wypełniają się kolorem marki — w prfct kolor oznacza stan i zaznaczenie, a czerń jest zarezerwowana dla akcji.
Przykłady
Z opisem
Umieść etykietę i opis w FieldContent obok pola wyboru. Opis wyjaśnia konsekwencje, a etykieta pozostaje na tyle krótka, żeby dało się ją szybko przejrzeć wzrokiem.
Kontynuując, akceptujesz nasz Regulamin i potwierdzasz zapoznanie się z Polityką prywatności.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field"
export default function CheckboxDescription() {
return (
<Field orientation="horizontal" className="max-w-sm">
<Checkbox id="terms" defaultChecked />
<FieldContent>
<FieldLabel htmlFor="terms">Akceptuję regulamin</FieldLabel>
<FieldDescription>
Kontynuując, akceptujesz nasz Regulamin i potwierdzasz zapoznanie się z Polityką prywatności.
</FieldDescription>
</FieldContent>
</Field>
)
}Stan pośredni
Nadrzędne pole wyboru, które steruje grupą, jest w stanie pośrednim, gdy zaznaczona jest tylko część pól podrzędnych. Ustaw indeterminate razem z checked; kliknięcie pola nadrzędnego zaznacza wtedy albo czyści wszystkie pola podrzędne. Dodaj polom podrzędnym wcięcie, żeby hierarchia była widoczna, a nie tylko sugerowana.
"use client"
import * as React from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
const permissions = [
{ id: "read", label: "Odczyt zawartości repozytorium" },
{ id: "write", label: "Wypychanie commitów i otwieranie pull requestów" },
{ id: "admin", label: "Zarządzanie ustawieniami i webhookami" },
]
export default function CheckboxIndeterminate() {
const [granted, setGranted] = React.useState<string[]>(["read"])
const all = granted.length === permissions.length
const some = granted.length > 0 && !all
return (
<FieldGroup className="w-80 gap-3">
<Field orientation="horizontal">
<Checkbox
id="permissions-all"
checked={all}
indeterminate={some}
onCheckedChange={(checked) =>
setGranted(checked ? permissions.map((p) => p.id) : [])
}
/>
<FieldLabel htmlFor="permissions-all">Wszystkie uprawnienia</FieldLabel>
</Field>
<div className="ml-2 flex flex-col gap-3 border-l pl-5">
{permissions.map((permission) => (
<Field key={permission.id} orientation="horizontal">
<Checkbox
id={`permission-${permission.id}`}
checked={granted.includes(permission.id)}
onCheckedChange={(checked) =>
setGranted((current) =>
checked
? [...current, permission.id]
: current.filter((id) => id !== permission.id)
)
}
/>
<FieldLabel
htmlFor={`permission-${permission.id}`}
className="font-normal"
>
{permission.label}
</FieldLabel>
</Field>
))}
</div>
</FieldGroup>
)
}Stany
Wyłączone pola wyboru blakną do połowy krycia i ignorują interakcję. Przy błędzie oznacz pole wyboru atrybutem aria-invalid, a otaczający je Field — atrybutem data-invalid; w FieldError wyjaśnij, co zrobić — sama czerwona ramka nie wystarczy.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldContent,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
export default function CheckboxStates() {
return (
<FieldGroup className="w-80 gap-4">
<Field orientation="horizontal" data-disabled>
<Checkbox id="state-disabled" disabled />
<FieldLabel htmlFor="state-disabled" className="font-normal">
Wyłączone
</FieldLabel>
</Field>
<Field orientation="horizontal" data-disabled>
<Checkbox id="state-disabled-checked" disabled defaultChecked />
<FieldLabel htmlFor="state-disabled-checked" className="font-normal">
Wyłączone i zaznaczone
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<Checkbox id="state-invalid" aria-invalid required />
<FieldContent>
<FieldLabel htmlFor="state-invalid">
Akceptuję warunki przetwarzania danych
</FieldLabel>
<FieldError>Aby kontynuować, musisz zaakceptować warunki.</FieldError>
</FieldContent>
</Field>
</FieldGroup>
)
}Karty wyboru
Owiń cały Field w FieldLabel, żeby zamienić go w kartę do zaznaczenia: cała karta przełącza pole wyboru, a zaznaczona karta nabiera odcienia marki. Karty stosuj dla opcji, które potrzebują ceny, obrazu lub dłuższego opisu.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldTitle,
} from "@/components/ui/field"
const addons = [
{
id: "backups",
title: "Codzienne kopie zapasowe",
description: "Przywracanie do dowolnego momentu z ostatnich 30 dni.",
price: "+12 $/mies.",
checked: true,
},
{
id: "analytics",
title: "Analityka witryny",
description: "Raporty ruchu i konwersji z poszanowaniem prywatności.",
price: "+$9/mo",
checked: false,
},
]
export default function CheckboxChoiceCard() {
return (
<div className="flex w-full max-w-md flex-col gap-3">
{addons.map((addon) => (
<FieldLabel key={addon.id} htmlFor={`addon-${addon.id}`}>
<Field orientation="horizontal">
<Checkbox id={`addon-${addon.id}`} defaultChecked={addon.checked} />
<FieldContent>
<FieldTitle>{addon.title}</FieldTitle>
<FieldDescription>{addon.description}</FieldDescription>
</FieldContent>
<span className="text-sm font-medium text-foreground tabular-nums">
{addon.price}
</span>
</Field>
</FieldLabel>
))}
</div>
)
}Grupa
Powiązane pola wyboru umieść w FieldSet z FieldLegend, żeby technologie wspomagające odczytały pytanie — Powiadom mnie e-mailem, gdy… — raz, przy wejściu do grupy, zamiast zostawiać każdą opcję bez kontekstu. Nadaj im ten sam name i różne wartości value, żeby wysłać je jako listę.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/field"
const events = [
{ id: "deploy-succeeded", label: "Wdrożenie się powiedzie", checked: false },
{ id: "deploy-failed", label: "Wdrożenie się nie powiedzie", checked: true },
{ id: "domain-expiring", label: "Domena wkrótce wygaśnie", checked: true },
{ id: "usage-limit", label: "Wykorzystanie osiągnie limit", checked: true },
]
export default function CheckboxFieldset() {
return (
<FieldSet className="w-full max-w-sm">
<FieldLegend variant="label">Wyślij mi e-mail, gdy</FieldLegend>
<FieldDescription>
Na każde zdarzenie wysyłamy najwyżej jeden e-mail na godzinę.
</FieldDescription>
<FieldGroup data-slot="checkbox-group" className="gap-3">
{events.map((event) => (
<Field key={event.id} orientation="horizontal">
<Checkbox
id={event.id}
name="events"
value={event.id}
defaultChecked={event.checked}
/>
<FieldLabel htmlFor={event.id} className="font-normal">
{event.label}
</FieldLabel>
</Field>
))}
</FieldGroup>
</FieldSet>
)
}Wytyczne
Kiedy używać
- Do pojedynczej, niezależnej zgody: Zapamiętaj mnie, Akceptuję regulamin.
- Do wyboru dowolnej liczby opcji z listy, także żadnej.
- Gdy zmiana zaczyna obowiązywać dopiero po wysłaniu formularza.
Kiedy nie używać
- Do ustawień, które działają od razu — użyj komponentu Switch.
- Do wyboru dokładnie jednej opcji z zestawu — użyj komponentu Radio Group.
- Gdy opcji jest więcej niż około dziesięciu — użyj komponentu Combobox lub Select z wyborem wielokrotnym.
Etykiety
Formułuj etykiety twierdząco, tak by opisywały stan zaznaczony. Nikt nie powinien rozszyfrowywać podwójnego przeczenia, żeby wiedzieć, co zrobi zaznaczenie pola.
Wysyłaj mi aktualności o produkcie
Nie przestawaj wysyłać mi aktualności
Układ
Układaj pola wyboru pionowo, po jednym w wierszu, z polem po lewej stronie etykiety. Pionowe listy szybciej się przegląda, a wszystkie etykiety są w nich wyrównane; poziome rzędy sprawdzają się tylko przy dwóch, trzech bardzo krótkich opcjach.
Dostępność
Widoczna kontrolka ma role="checkbox" i aria-checked ("mixed" w stanie pośrednim). Base UI renderuje ukryte natywne pole input na potrzeby wysyłania i walidacji formularza, przypisuje mu id pola wyboru i łączy sąsiedni <label htmlFor> z widoczną kontrolką przez aria-labelledby — dzięki temu etykiety są zarówno klikalne, jak i odczytywane.
| Klawisz | Działanie |
|---|---|
Tab | Przenosi fokus na pole wyboru. Fokus widać jako pierścień 2 px odsunięty od pola. |
Spacja | Przełącza pole wyboru. |
- Zawsze dodawaj etykietę. Użyj
FieldLabelzhtmlFor, owiń pole wyboru etykietą albo użyjaria-label— ale tylko wtedy, gdy widoczna etykieta byłaby naprawdę zbędna (np. pole zaznaczania wiersza w tabeli z czytelnym nagłówkiem kolumny). - Krawędzie. Obramowanie niezaznaczonego pola używa tokenu
input— 3:1 względem strony, zgodnie z WCAG 1.4.11. Stan zaznaczenia przekazuje też znacznik, a nie sam kolor. - Rozmiar celu. Pole ma 16 px, ale niewidoczny obszar kliknięcia powiększa je do 24 × 24 px (WCAG 2.2, 2.5.8), nie wpływając na układ. Pole przełącza też kliknięcie etykiety.
- Grupy. Użyj
FieldSetiFieldLegend, żeby pytanie grupy zostało odczytane. W hierarchii rodzic–dzieciaria-checked="mixed"na polu nadrzędnym informuje użytkowników czytników ekranu, że zaznaczono tylko część pól podrzędnych.
Dokumentacja API
Checkbox
Renderuje <span role="checkbox"> i ukryty <input>. Przyjmuje wszystkie propsy Checkbox.Root z Base UI.