Native Select
Natywny element select przeglądarki, dopasowany wyglądem do prfct — najlepszy na urządzeniach mobilnych, w długich formularzach i wszędzie tam, gdzie kontrolka ma działać bez JavaScriptu.
import { Field, FieldLabel } from "@/components/ui/field"
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"
export default function NativeSelectDemo() {
return (
<Field className="w-64">
<FieldLabel htmlFor="native-select-demo">Okres rozliczeniowy</FieldLabel>
<NativeSelect
id="native-select-demo"
defaultValue="yearly"
className="w-full"
>
<NativeSelectOption value="monthly">Miesięcznie</NativeSelectOption>
<NativeSelectOption value="quarterly">Kwartalnie</NativeSelectOption>
<NativeSelectOption value="yearly">
Rocznie · 20% taniej
</NativeSelectOption>
</NativeSelect>
</Field>
)
}Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/native-select.jsonUżycie
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/components/ui/native-select"<NativeSelect defaultValue="yearly">
<NativeSelectOption value="monthly">Monthly</NativeSelectOption>
<NativeSelectOption value="yearly">Yearly</NativeSelectOption>
</NativeSelect>className stylizuje kontener, który domyślnie ma w-fit — aby ustalić szerokość kontrolki, przekaż np. w-full. Wszystkie pozostałe propsy trafiają do elementu <select>.
Przykłady
Rozmiary
Rozmiary sm, default i lg mają te same wysokości co Input i Button, więc filtr i powiązana z nim akcja układają się w jednej linii.
import { Button } from "@/components/ui/button"
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"
const sizes = ["sm", "default", "lg"] as const
export default function NativeSelectSizes() {
return (
<div className="flex flex-col gap-4">
{sizes.map((size) => (
<div key={size} className="flex items-center gap-2">
<NativeSelect
size={size}
defaultValue="7"
aria-label={`Zakres dat (${size})`}
className="w-48"
>
<NativeSelectOption value="1">Ostatnie 24 godziny</NativeSelectOption>
<NativeSelectOption value="7">Ostatnie 7 dni</NativeSelectOption>
<NativeSelectOption value="30">Ostatnie 30 dni</NativeSelectOption>
</NativeSelect>
<Button size={size} variant="outline">
Eksportuj
</Button>
</div>
))}
</div>
)
}Grupy opcji
NativeSelectOptGroup dodaje do długich list nagłówki, których nie da się wybrać. Systemy operacyjne renderują je natywnie — z wcięciem na komputerach, jako osobne sekcje w mobilnych oknach wyboru.
Używana w zaplanowanych raportach i przypomnieniach.
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/components/ui/native-select"
const zones = {
Americas: [
["America/New_York", "Nowy Jork (UTC−4)"],
["America/Chicago", "Chicago (UTC−5)"],
["America/Los_Angeles", "Los Angeles (UTC−7)"],
],
Europe: [
["Europe/London", "Londyn (UTC+1)"],
["Europe/Warsaw", "Warszawa (UTC+2)"],
["Europe/Helsinki", "Helsinki (UTC+3)"],
],
"Asia Pacific": [
["Asia/Singapore", "Singapur (UTC+8)"],
["Asia/Tokyo", "Tokio (UTC+9)"],
["Australia/Sydney", "Sydney (UTC+10)"],
],
}
export default function NativeSelectGroups() {
return (
<Field className="w-72">
<FieldLabel htmlFor="native-select-zone">Strefa czasowa</FieldLabel>
<NativeSelect
id="native-select-zone"
defaultValue="Europe/Warsaw"
className="w-full"
>
{Object.entries(zones).map(([region, options]) => (
<NativeSelectOptGroup key={region} label={region}>
{options.map(([value, label]) => (
<NativeSelectOption key={value} value={value}>
{label}
</NativeSelectOption>
))}
</NativeSelectOptGroup>
))}
</NativeSelect>
<FieldDescription>
Używana w zaplanowanych raportach i przypomnieniach.
</FieldDescription>
</Field>
)
}Nieprawidłowy i wyłączony
Pusta, wyłączona pierwsza opcja działa jak placeholder: w połączeniu z required przeglądarka nie wyśle formularza, dopóki ktoś nie dokona prawdziwego wyboru. Nieprawidłowy wybór oznacz atrybutem aria-invalid i komunikatem FieldError — dokładnie tak jak w polu tekstowym.
import {
Field,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"
export default function NativeSelectStates() {
return (
<FieldGroup className="grid w-full max-w-xl gap-5 sm:grid-cols-2">
<Field data-invalid>
<FieldLabel htmlFor="native-select-invalid">Kraj</FieldLabel>
<NativeSelect
id="native-select-invalid"
defaultValue=""
required
aria-invalid
aria-describedby="native-select-invalid-error"
className="w-full"
>
<NativeSelectOption value="" disabled>
Wybierz kraj
</NativeSelectOption>
<NativeSelectOption value="pl">Polska</NativeSelectOption>
<NativeSelectOption value="de">Niemcy</NativeSelectOption>
</NativeSelect>
<FieldError id="native-select-invalid-error">
Wybierz kraj, w którym płacisz podatki.
</FieldError>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="native-select-disabled">Waluta</FieldLabel>
<NativeSelect
id="native-select-disabled"
defaultValue="eur"
disabled
className="w-full"
>
<NativeSelectOption value="eur">Euro (EUR)</NativeSelectOption>
</NativeSelect>
</Field>
</FieldGroup>
)
}Wytyczne
Kiedy używać
- W procesach używanych głównie na urządzeniach mobilnych: telefon otwiera wtedy systemowe okno wyboru, które jest szybsze i dobrze znane.
- W długich formularzach i na stronach renderowanych na serwerze, które muszą działać, zanim załaduje się JavaScript — albo całkiem bez niego.
- W prostych listach opcji tekstowych, w których własne renderowanie niczego nie wnosi.
Kiedy nie używać
- Gdy opcje potrzebują ikon, opisów lub własnego układu — użyj Select.
- Gdy lista jest na tyle długa, że przydaje się wyszukiwanie — użyj Combobox.
- Gdy od dwóch do pięciu opcji trzeba porównać jednym spojrzeniem — użyj Radio Group lub Toggle Group.
Natywny czy własny?
Wybierz jedno rozwiązanie dla danego obszaru produktu i trzymaj się go konsekwentnie — oba naraz w jednym formularzu sprawiają wrażenie niedokończonej pracy.
Dostępność
Native Select to prawdziwy <select>, więc bez jednej linijki skryptu przejmuje z platformy obsługę klawiatury, semantykę dla czytników ekranu i zachowanie w formularzach.
- Nadaj mu etykietę — przez
FieldLabelzhtmlFor/idalbo, w kompaktowych filtrach, przezaria-label. - Kolory systemowe. Opcje używają kolorów systemowych
CanvasiCanvasText, więc w trybie ciemnym rozwinięta lista podąża za systemem operacyjnym i zacolor-schemestrony. - Kontrast. Obramowanie korzysta z tokenu
inputo kontraście 3:1 względem strony; ikona strzałki jest dekoracyjna i ukryta przed technologiami wspomagającymi.
| Klawisz | Działanie |
|---|---|
Tab | Przenosi fokus na listę wyboru. |
SpacjaEnterAlt↓ | Rozwija listę (zależnie od platformy). |
↑↓ | Zmienia wybór — w większości przeglądarek na komputerach bez rozwijania listy. |
A–Z | Przeskakuje do następnej opcji zaczynającej się na tę literę. |
Dokumentacja API
NativeSelect
Renderuje kontener <div> z elementem <select> i ikoną strzałki. Przyjmuje wszystkie natywne propsy elementu select.
NativeSelectOption
Renderuje element <option>. Przyjmuje wszystkie natywne propsy opcji, w tym value i disabled.
NativeSelectOptGroup
Renderuje element <optgroup>. W label podaj nagłówek grupy.