<button>
, <input>
, <nav>
, <form>
.role="button"
cho nút bấmrole="dialog"
cho hộp thoạiaria-expanded="true"
: phần tử có đang mở hay khôngaria-checked="false"
: trạng thái bật/tắt của togglearia-labelledby="title"
liên kết label từ phần tử khácrole="switch"
cho biết đây là công tắc.aria-checked
phản ánh trạng thái bật/tắt hiện tại.tabIndex={0}
để phần tử này có thể nhận được focus bằng bàn phím.onKeyDown
cho phím Enter và Space để người dùng có thể thao tác bằng bàn phím.import { useState, useRef, useEffect } from "react";
const options = ["Apple", "Banana", "Cherry"];
function Dropdown() { const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState(null); const [highlightedIndex, setHighlightedIndex] = useState(0); const dropdownRef = useRef(null);
useEffect(() => { if (isOpen && dropdownRef.current) dropdownRef.current.focus(); }, [isOpen]);
const handleKeyDown = (e) => { if (!isOpen && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); setIsOpen(true); return; }
if (isOpen) { switch (e.key) { case "ArrowDown": e.preventDefault(); setHighlightedIndex((i) => (i + 1) % options.length); break; case "ArrowUp": e.preventDefault(); setHighlightedIndex((i) => (i - 1 + options.length) % options.length); break; case "Enter": e.preventDefault(); setSelected(options[highlightedIndex]); setIsOpen(false); break; case "Escape": setIsOpen(false); break; default: break; } } };
return ( <div> <div role="button" aria-haspopup="listbox" aria-expanded={isOpen} aria-controls="dropdown-list" tabIndex={0} onClick={() => setIsOpen((o) => !o)} onKeyDown={handleKeyDown} style={{ padding: "10px", background: "#eee", border: "1px solid #ccc", width: "200px", cursor: "pointer", }} > {selected || "Select an option"} </div> {isOpen && ( <ul id="dropdown-list" role="listbox" ref={dropdownRef} tabIndex={-1} style={{ listStyle: "none", margin: 0, padding: 0, border: "1px solid #ccc", background: "white", }} > {options.map((opt, i) => ( <li key={opt} role="option" aria-selected={highlightedIndex === i} onClick={() => { setSelected(opt); setIsOpen(false); }} style={{ padding: "10px", background: highlightedIndex === i ? "#007bff" : "white", color: highlightedIndex === i ? "white" : "black", }} > {opt} </li> ))} </ul> )} </div> );}
Thuộc tính | Mục đích |
---|---|
role="button" | Khai báo phần tử kích hoạt dropdown |
aria-haspopup="listbox" | Báo cáo có popup danh sách lựa chọn |
aria-expanded | Tình trạng mở/đóng dropdown |
role="listbox" và role="option" | Xác định cấu trúc dropdown |
aria-label
: Gắn nhãn ngay trên phần tử.<input type="text" aria-label="Tên người dùng" />
aria-labelledby
: Tham chiếu đến id phần tử chứa nhãn.<span id="label1">Tên đăng nhập</span><input type="text" aria-labelledby="label1" />
aria-expanded
, aria-checked
để quản lý trạng thái.<div>
và ARIA một cách tùy tiện.aria-hidden="true"
cho phần tử tương tác.Vai trò (Role) | Ý nghĩa |
---|---|
button | Phần tử tương tác dạng nút |
dialog | Hộp thoại/modal |
tab , tabpanel | Giao diện tabs |
listbox , option | Dropdown hoặc lựa chọn |
alert | Thông báo thay đổi quan trọng |
aria-label
hoặc aria-labelledby
để cung cấp tên phần tử hợp lệ.aria-expanded
hay aria-checked
.