Phần tử <select> gốc trước đây rất khó tạo kiểu và chỉ có thể chứa các tuỳ chọn văn bản thuần túy. Thuộc tính appearance: base-select cung cấp một cách khai báo, chỉ dùng CSS để chọn trạng thái có thể tuỳ chỉnh cho phần tử <select>. Điều này cho phép nhà phát triển đưa nội dung HTML đa dạng (chẳng hạn như hình ảnh, SVG và bố cục phức tạp) vào bên trong các phần tử <option>, đồng thời vẫn giữ được khả năng hỗ trợ tiếp cận bằng bàn phím gốc và tích hợp biểu mẫu. Sử dụng mẫu này để thay thế các thành phần chọn tuỳ chỉnh, nặng bằng các phần tử gốc, chuẩn.
Cách triển khai
Cách triển khai công cụ chọn nội dung đa phương tiện bằng Customizable Select API:
- Chọn sử dụng kiểu cơ bản: Áp dụng
appearance: base-selectcho cả phần tử<select>và bộ chọn nội bộ của phần tử đó bằng cách sử dụng phần tử giả::picker(select). Thao tác này sẽ thay đổi trình phân tích cú pháp HTML của trình duyệt cho nội dung bên trong<select>. - Xác định nội dung nút: Sử dụng các phần tử
<button>và<selectedcontent>chuẩn bên trong<select>để xác định nội dung hiển thị khi bộ chọn đóng. Phần tử<selectedcontent>tự động phản ánh nội dung của lựa chọn đã chọn. Bạn phải cung cấp thông tin này nếu muốn hiển thị nội dung đa dạng thức hiện được chọn trong nút. - Sử dụng nội dung đa dạng thức bên trong các lựa chọn: Giờ đây, bạn có thể đặt hình ảnh, SVG và các thẻ HTML khác bên trong thẻ
<option>. Trước khi có API này, trình duyệt sẽ xoá các thẻ khỏi thẻ<option>và chỉ hiển thị văn bản thuần túy. - Tạo kiểu cho Popover: Tạo kiểu cho danh sách lựa chọn thả xuống bằng cách nhắm đến phần tử giả
::picker(select). Nó hiển thị ở lớp trên cùng, nghĩa là bạn không cần phải xử lýz-index. Các lựa chọn được định vị bằng cách sử dụng API Định vị neo theo cách gốc.
Mã ví dụ: Bộ chọn vai trò đa dạng
<label for="role-picker">Select your role</label>
<select class="custom-select" name="role" id="role-picker">
<button>
<selectedcontent></selectedcontent> <!-- Mirrors the selected option's content automatically so you do not need JS to update the button -->
</button>
<!-- Define concise aria-label values on options whose mirrored rich content would otherwise read awkwardly as a concatenated string -->
<option value="frontend" aria-label="Frontend Developer">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
</svg>
<div class="option-text">
<span class="option-title">Frontend Developer</span>
<span class="option-desc">React, Vue, CSS</span>
</div>
</option>
<option value="backend" aria-label="Backend Developer">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="2" y="12" width="20" height="14" rx="2" ry="2"></rect>
</svg>
<div class="option-text">
<span class="option-title">Backend Developer</span>
<span class="option-desc">Node.js, Python</span>
</div>
</option>
</select>
select.custom-select,
select.custom-select::picker(select) {
appearance: base-select; /* MUST opt-in both the select and its picker to enable the customizable state, otherwise browser standard rendering applies */
}
select.custom-select {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 12px;
background-color: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
color: #f1f5f9;
cursor: pointer;
}
select.custom-select::picker(select) {
background-color: #0f172a;
border: 1px solid #334155;
border-radius: 8px;
padding: 8px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
width: anchor-size(width); /* Uses Anchor Positioning API to keep the dropdown precisely aligned to the button trigger width */
}
select.custom-select option {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
border-radius: 4px;
cursor: pointer;
}
select.custom-select option:hover {
background-color: #1e293b;
}
/* Remove standard OS checkmark for base-select */
select.custom-select option::before {
display: none;
}
/* MANDATORY: Provide multiple visual indicators (e.g., prominent background color and bold title font) to communicate the checked state cleanly */
select.custom-select option:checked {
background-color: #3b82f6;
color: #ffffff;
}
select.custom-select option:checked .option-title {
font-weight: 700;
}
Triển khai chiến lược và các phương pháp hay nhất
- NÊN sử dụng
appearance: base-selectkhi bạn cần bố cục tuỳ chọn phức tạp (biểu tượng, nội dung mô tả, hình ảnh) mà trước đây chỉ có thể thực hiện được bằng các khung giao diện người dùng JavaScript nặng. - KHÔNG sử dụng các phần tử đặc biệt nếu bạn nhận thấy hiệu suất bị trễ; trình duyệt sẽ xử lý tiêu điểm bàn phím gốc theo cách gốc.
- NÊN tính đến việc kết xuất lớp trên cùng. Bộ chọn hiển thị ở lớp trên cùng, tức là nó ghi đè
z-indextương đối của nội dung trang. - NÊN ẩn các chi tiết phụ (như nội dung mô tả) trong trạng thái nút nếu các chi tiết này chiếm quá nhiều không gian, bằng cách tạo kiểu cho
.custom-select selectedcontent .option-desc { display: none; }. - Kiểm thử hành vi của bố cục. Việc đặt
appearance: base-selectsẽ xoá hành vi mặc định của trình duyệt là định cỡ lựa chọn dựa trên chiều rộng của lựa chọn dài nhất. Bạn có thể cần đặt chiều rộng cố định hoặc sử dụng các quy tắc ràng buộc về flex/grid để ngăn bố cục thay đổi. - Đảm bảo
<select>của bạn có thuộc tínhnamevà<label>được liên kết. Điều này đảm bảo rằng ngay cả khi có giao diện người dùng tuỳ chỉnh, thành phần này vẫn có thể truy cập được đối với trình đọc màn hình và hoạt động chính xác với các lượt gửi biểu mẫu chuẩn. - Sử dụng các thuộc tính
valuechuẩn cho các lựa chọn để đảm bảo việc gửi biểu mẫu hoạt động chính xác như trước.
Chiến lược dự phòng
Phương án dự phòng và khả năng hỗ trợ của trình duyệt cho <select> có thể tuỳ chỉnh
Đối với các trình duyệt chưa hỗ trợ appearance: base-select, phần tử <select> sẽ hạ cấp một cách linh hoạt xuống trình đơn thả xuống của hệ điều hành tiêu chuẩn.
- Bỏ qua nội dung không phải văn bản: Các trình duyệt cũ loại bỏ thẻ HTML (như
<svg>hoặc<div>) bên trong thẻ<option>và chỉ hiển thị các nút văn bản. Đảm bảo nội dung văn bản của<option>dễ đọc và có ý nghĩa riêng. - Xử lý cấu trúc HTML: Trình phân tích cú pháp chuẩn có thể bỏ qua các thẻ
<button>và<selectedcontent>bên trong<select>hoặc coi các thẻ đó là không hợp lệ. Bạn không cần phải sử dụng các polyfill JavaScript nặng để cải tiến tăng dần nếu bạn xem văn bản chuẩn là một phương án dự phòng dễ đọc.
document.addEventListener("DOMContentLoaded", () => {
// Check if browser supports base-select value
if (!CSS.supports("appearance", "base-select")) {
// Custom select overrides are not supported natively.
}
});