Bỏ qua đến nội dung

Select gốc

Thành phần select HTML gốc được thiết kế lại dành cho biểu mẫu, bộ lọc và công cụ chọn quốc gia — menu thả xuống được hệ điều hành hiển thị với viền và bóng theo phong cách neobrutalist.

import {
  NativeSelect,
  NativeSelectOption,

Native select là một lớp tạo kiểu mỏng phủ lên chính thẻ <select> của trình duyệt — không primitive, không portal, không JavaScript. Phần điều khiển khi đóng mang công thức neobrutalist — viền dày, bóng đổ đậm và kiểu chữ đậm — còn danh sách khi mở vẫn là bộ chọn của hệ điều hành.

Nên dùng khi:

  • Bộ chọn quốc gia, múi giờ và tiền tệ — những danh sách tùy chọn rất dài mà hệ điều hành render nhanh hơn bất kỳ popover tùy chỉnh nào.
  • Biểu mẫu chủ yếu dùng trên mobile — iOS và Android thay bằng bánh xe chọn native của chúng, tiện hơn hẳn một dropdown tùy chỉnh trên màn hình nhỏ.
  • Gửi form thuần — đây là một <select name="…"> thật, nên giá trị được gửi kèm form và hoạt động cả trước khi hydrate.

Cài đặt

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/native-select.json
npx shadcn@latest add https://neobrutalism.com/r/base/native-select.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/native-select.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/native-select.json

Sao chép và dán đoạn mã sau vào dự án của bạn.

components/ui/native-select.tsx
import * as React from "react"
import { ChevronDownIcon } from "lucide-react"

import { cn } from "@/lib/utils"

type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
  size?: "sm" | "default"
}

function NativeSelect({
  className,
  size = "default",
  ...props
}: NativeSelectProps) {
  return (
    <div
      className={cn(
        "group/native-select relative w-fit has-[select:disabled]:opacity-50",
        className
      )}
      data-slot="native-select-wrapper"
      data-size={size}
    >
      <select
        data-slot="native-select"
        data-size={size}
        className="h-8 w-full min-w-0 appearance-none rounded border-2 bg-input py-2 pr-8 pl-3 text-sm shadow-sm transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive data-[size=sm]:h-7 data-[size=sm]:rounded data-[size=sm]:py-0.5"
        {...props}
      />
      <ChevronDownIcon
        className="pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted-foreground select-none"
        aria-hidden="true"
        data-slot="native-select-icon"
      />
    </div>
  )
}

function NativeSelectOption({
  className,
  ...props
}: React.ComponentProps<"option">) {
  return (
    <option
      data-slot="native-select-option"
      className={cn("bg-[Canvas] text-[CanvasText]", className)}
      {...props}
    />
  )
}

function NativeSelectOptGroup({
  className,
  ...props
}: React.ComponentProps<"optgroup">) {
  return (
    <optgroup
      data-slot="native-select-optgroup"
      className={cn("bg-[Canvas] text-[CanvasText]", className)}
      {...props}
    />
  )
}

export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }

Cập nhật các đường dẫn import cho phù hợp với thiết lập dự án của bạn.

Cách dùng

import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@/components/ui/native-select"
<NativeSelect>
  <NativeSelectOption value="">Select a fruit</NativeSelectOption>
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  <NativeSelectOption value="banana">Banana</NativeSelectOption>
  <NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
  <NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
</NativeSelect>

Kết hợp

Đơn giản

Các tùy chọn được đặt trực tiếp bên trong NativeSelect (không có NativeSelectOptGroup).

NativeSelect
├── NativeSelectOption
├── NativeSelectOption
├── NativeSelectOption
└── NativeSelectOption

Với nhóm

Dùng NativeSelectOptGroup để sắp xếp các tùy chọn thành các danh mục.

NativeSelect
├── NativeSelectOptGroup
│   ├── NativeSelectOption
│   └── NativeSelectOption
└── NativeSelectOptGroup
    ├── NativeSelectOption
    └── NativeSelectOption

Ví dụ

Nhóm

Dùng NativeSelectOptGroup để sắp xếp các tùy chọn thành các danh mục.

import {
  NativeSelect,
  NativeSelectOptGroup,

Vô hiệu hóa

Thêm prop disabled vào component NativeSelect để vô hiệu hóa select.

import {
  NativeSelect,
  NativeSelectOption,

Không hợp lệ

Dùng aria-invalid để hiển thị lỗi xác thực và thuộc tính data-invalid trên component Field để tạo kiểu.

import {
  NativeSelect,
  NativeSelectOption,

Native Select vs Select

  • Dùng NativeSelect cho hành vi gốc của trình duyệt, hiệu năng tốt hơn hoặc các danh sách thả xuống tối ưu cho di động.
  • Dùng Select cho kiểu dáng tùy chỉnh, hoạt ảnh hoặc các tương tác phức tạp.

RTL

Để bật hỗ trợ RTL trong Neobrutalism, hãy xem hướng dẫn cấu hình RTL.

"use client"

import * as React from "react"

Khả năng truy cập

Đây chính là thẻ <select> của nền tảng, thứ mà trình duyệt vốn đã phơi bày sẵn cho công nghệ hỗ trợ — cũng chính là điều khiển mà mẫu combobox chỉ-chọn của WAI-ARIA tồn tại để mô phỏng. Không cần kết nối ARIA gì cả; chỉ cần cho nó một tên gọi truy cập được qua <Label htmlFor> hoặc aria-label.

Hành vi bàn phím do trình duyệt cung cấp, nên các tổ hợp phím khác nhau đôi chút tùy hệ điều hành và trình duyệt. Bộ phím phổ biến:

PhímHành động
Space / Alt + ArrowDownMở danh sách tùy chọn
ArrowDown / ArrowUpLàm nổi bật tùy chọn kế tiếp / trước đó
Home / EndNhảy đến tùy chọn đầu / cuối
EnterChốt tùy chọn đang được làm nổi bật và đóng danh sách
EscapeĐóng danh sách mà không thay đổi giá trị
Ký tự in đượcTypeahead — nhảy đến tùy chọn kế tiếp khớp với những gì bạn gõ

Tham chiếu API

NativeSelect

Component select chính, bao bọc phần tử select HTML gốc.

<NativeSelect>
  <NativeSelectOption value="option1">Option 1</NativeSelectOption>
  <NativeSelectOption value="option2">Option 2</NativeSelectOption>
</NativeSelect>

NativeSelectOption

Đại diện cho một tùy chọn riêng lẻ bên trong select.

PropTypeDefault
valuestring
disabledbooleanfalse

NativeSelectOptGroup

Nhóm các tùy chọn liên quan với nhau để sắp xếp tốt hơn.

PropTypeDefault
labelstring
disabledbooleanfalse
<NativeSelectOptGroup label="Fruits">
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  <NativeSelectOption value="banana">Banana</NativeSelectOption>
</NativeSelectOptGroup>