Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

Select

Chọn một giá trị từ một danh sách. Trigger trông như Input, cùng bốn size; danh sách mở trên trigger, trên nền raised, với giá trị hiện tại căn thẳng trigger và được đánh dấu bằng check.

  • npm: 3 trực tiếp, 4 tổng
  • registry: 2 trực tiếp, 2 tổng

Xem trước

Props

  • size
  • disabled
  • placeholder
  • defaultValue
  • value
  • open
  • defaultOpen
  • dir
  • label
  • groups
  • aria-invalid
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-select" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="preview-select">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>

Cách dùng

Khi nào dùng

  • Chọn một giá trị từ danh sách phương án đã biết, như vai trò của một thành viên nhóm.
  • Vừa một lựa chọn vào chỗ của một field, cạnh Input hoặc Button cùng size.
  • Danh sách dài hơn được chia thành các nhóm có tên, với một đường giữa chúng.

Khi nào không dùng

  • Lựa chọn có hoặc không. Dùng checkbox native.
  • Chuyển sang trang khác hoặc chạy một hành động. Dùng link hoặc Button.
  • Chữ người dùng gõ tự do. Dùng Input.

Biến thể

size

Chiều cao trigger theo size-element-*, đặt trên SelectTrigger: sm 28px, md 32px (mặc định), lg 36px, xl 44px với chuột; trên cảm ứng md là 36px, lg 44px và xl 48px. Khớp Input hoặc Button cạnh nó: md trong form, sm chỉ trong UI đặc ưu tiên chuột, lg hoặc xl cho field chính của một màn hình.

  • sm

  • mdmặc định

  • lg

  • xl

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-select-size-sm" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="preview-select-size-sm" size="sm">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-select-size-md" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="preview-select-size-md">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-select-size-lg" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="preview-select-size-lg" size="lg">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>
<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="preview-select-size-xl" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="preview-select-size-xl" size="xl">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>

Ví dụ

Mặc định

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="member-role" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="member-role">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
      <SelectItem value="admin">Quản trị</SelectItem>
    </SelectContent>
  </Select>
</div>

Phương án theo nhóm

Mỗi nhóm được heading của nó đặt tên; đường giữa các nhóm chỉ để trang trí.

import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="member-access" className="text-sm text-text-primary">Quyền truy cập</label>
  <Select>
    <SelectTrigger id="member-access">
      <SelectValue placeholder="Chọn quyền truy cập" />
    </SelectTrigger>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Không gian làm việc</SelectLabel>
        <SelectItem value="viewer">Người xem</SelectItem>
        <SelectItem value="editor">Biên tập</SelectItem>
      </SelectGroup>
      <SelectSeparator />
      <SelectGroup>
        <SelectLabel>Thanh toán</SelectLabel>
        <SelectItem value="billing-viewer">Người xem thanh toán</SelectItem>
        <SelectItem value="billing-admin">Quản trị thanh toán</SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>
</div>

Disabled

Vẫn hiện giá trị nhưng không mở được.

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="locked-role" className="text-sm text-text-primary">Vai trò</label>
  <Select defaultValue="owner" disabled>
    <SelectTrigger id="locked-role">
      <SelectValue />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="owner">Chủ sở hữu</SelectItem>
    </SelectContent>
  </Select>
</div>

Không hợp lệ

Viền danger cùng chữ lỗi liên kết bằng aria-describedby.

Chọn vai trò cho thành viên mới.

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"

<div className="flex w-full max-w-sm flex-col gap-xs">
  <label htmlFor="invite-role" className="text-sm text-text-primary">Vai trò</label>
  <Select>
    <SelectTrigger id="invite-role" aria-invalid aria-describedby="invite-role-error">
      <SelectValue placeholder="Chọn vai trò" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="viewer">Người xem</SelectItem>
      <SelectItem value="editor">Biên tập</SelectItem>
    </SelectContent>
  </Select>
  <p id="invite-role-error" className="text-sm text-text-danger">Chọn vai trò cho thành viên mới.</p>
</div>

Nên và không nên

Nên

  • Dùng Select để chọn một giá trị từ danh sách phương án đã biết.
  • Luôn đi cùng trigger với một label nhìn thấy (htmlFor / id).
  • Giữ label phương án ngắn và ở sentence case, và sắp theo cách người dùng kỳ vọng (theo thứ tự dùng, hoặc theo bảng chữ cái).
  • Dùng placeholder chỉ để gợi hành động (Chọn một vai trò), không bao giờ thay cho label.
  • Khớp size của trigger với Input hoặc Button cạnh nó.
  • Chia danh sách dài thành các nhóm, mỗi nhóm một heading SelectLabel (Không gian làm việc, Thanh toán), và một SelectSeparator giữa chúng.

Không nên

  • Đừng dùng Select cho lựa chọn có hoặc không. Dùng checkbox.
  • Đừng dùng Select để điều hướng hoặc để kích hoạt hành động. Dùng link hoặc Button.
  • Đừng đặt icon, description hoặc control khác bên trong phương án; một phương án là một dòng chữ.
  • Đừng dùng size sm trên màn hình ưu tiên cảm ứng.
  • Đừng style lại màu qua className. Nếu một trạng thái trông sai, mở một issue thay vì ghi đè.

Props

TênKiểuGiá trịMặc địnhMô tả
sizeenumsm | md | lg | xlmdChiều cao trigger theo size-element-*, đặt trên SelectTrigger: sm 28px, md 32px (mặc định), lg 36px, xl 44px với chuột; trên cảm ứng md là 36px, lg 44px và xl 48px. Khớp Input hoặc Button cạnh nó: md trong form, sm chỉ trong UI đặc ưu tiên chuột, lg hoặc xl cho field chính của một màn hình.
disabledboolean-falseĐặt trên Select. Trigger không focus được và không mở được, đồng thời dùng diện mạo disabled trung tính.
placeholderstring--Đặt trên SelectValue. Hiện trong trigger đến khi chọn một giá trị. Không phải label.
defaultValuestring--Đặt trên Select. Giá trị được chọn ở lần render đầu, khi bạn không kiểm soát nó.
valuestring--Đặt trên Select. Giá trị đã chọn, khi bạn kiểm soát nó. Đi cùng onValueChange.
onValueChangefunction--Đặt trên Select. Được gọi với giá trị mới khi người dùng chọn một phương án.
namestring--Đặt trên Select. Gửi giá trị cùng form dưới tên này. Chỉ trên web.
requiredboolean-falseĐặt trên Select. Form không gửi được đến khi chọn một giá trị. Nói điều này trong label nữa, không chỉ bằng prop này. Chỉ trên web.
openboolean--Đặt trên Select. Danh sách có đang mở hay không, khi bạn kiểm soát nó. Đi cùng onOpenChange.
defaultOpenboolean-falseĐặt trên Select. Mở danh sách ở lần render đầu, khi bạn không kiểm soát nó.
onOpenChangefunction--Đặt trên Select. Được gọi với true hoặc false khi danh sách mở hoặc đóng.
direnumltr | rtl-Đặt trên Select. Hướng đọc của danh sách và bàn phím của nó. Bỏ qua để theo trang. Chỉ trên web.

Slot

TênBắt buộcMô tả
triggercóSelectTrigger với một SelectValue bên trong.
contentcóSelectContent với một SelectItem cho mỗi phương án. Label của item là danh từ ngắn ở sentence case. Danh sách dài: SelectGroup với một SelectLabel cho mỗi nhóm, SelectSeparator giữa các nhóm.

Cấu tạo

PhầnMô tả
rootSelect. Giữ giá trị và trạng thái mở; bản thân không render gì.
triggerSelectTrigger: button hiện giá trị và mở danh sách. Cùng nền, viền và size với Input.
valueSelectValue: chữ của phương án hiện tại, hoặc placeholder khi chưa chọn gì.
iconTabler ChevronDownIcon ở cuối trigger. 16px ở sm, md và lg, 20px ở xl (DESIGN.md 9.1).
contentSelectContent: danh sách nổi trên nền raised, rộng ít nhất bằng trigger. Danh sách dài cuộn bên trong.
itemSelectItem: một phương án. Được tô sáng khi hover và khi dùng phím mũi tên.
indicatorTabler CheckIcon (16px) ở cuối phương án đã chọn.
optionGroupSelectGroup: các phương án dưới một heading. Không render hộp riêng.
groupLabelSelectLabel: heading của một nhóm, nhỏ hơn một phương án, chữ secondary. Không chọn được.
separatorSelectSeparator: một đường ngang suốt danh sách, giữa hai nhóm.
scrollButtonSelectContent hiện một caret ở trên hoặc dưới khi còn phương án ngoài tầm nhìn; trỏ vào caret thì danh sách cuộn. Tabler ChevronUpIcon hoặc CaretDownIcon, 16px.
hitArea::before trong suốt, căn giữa trigger, không nhỏ hơn size-target-min (24px với chuột, 44px với cảm ứng), nên sm vẫn là đích chạm 44px.

Khả năng truy cập

Role: combobox

PhímHành động
TabĐưa focus tới trigger và hiện focus ring.
Enter, Space, ArrowDown, ArrowUpMở danh sách với giá trị hiện tại được tô sáng.
ArrowDown, ArrowUpDi chuyển vùng tô sáng khi danh sách đang mở.
Home, EndTô sáng phương án đầu hoặc phương án cuối.
Enter, SpaceChọn phương án đang tô sáng, đóng danh sách và trả focus về trigger.
EscapeĐóng danh sách mà không đổi giá trị và trả focus về trigger.
A to ZTô sáng phương án kế tiếp bắt đầu bằng các chữ vừa gõ.
  • Cho trigger một label nhìn thấy: một id trên SelectTrigger và một label với htmlFor. aria-label chỉ khi tên được hiện theo cách khác, như tiêu đề cột của một bảng.
  • Không hợp lệ: aria-invalid trên SelectTrigger cùng chữ lỗi liên kết bằng aria-describedby. Không bao giờ chỉ dùng màu.
  • Disabled dùng attribute disabled native trên trigger, nên trigger rời khỏi thứ tự Tab.
  • Trigger là một button với role combobox và danh sách là listbox; các phương án được đọc kèm trạng thái selected.
  • Chỉ một giá trị. Với nhiều giá trị thì dùng một nhóm checkbox.
  • SelectGroup có role group và được SelectLabel đặt tên, nên trình đọc màn hình đọc heading khi vùng tô sáng đi vào nhóm. SelectLabel không focus được và phím mũi tên bỏ qua nó.
  • SelectSeparator chỉ để trang trí (aria-hidden).

Cài đặt

npx werewolf-ui add select

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.