-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
π μ»΄ν¬λνΈ μ 보
- μ»΄ν¬λνΈ μ΄λ¦: Select
- μ»΄ν¬λνΈ μ€λͺ : Select μ»΄ν¬λνΈλ μ¬μ©μκ° μ νλ μ΅μ μ§ν©μμ νλ μ΄μμ μ΅μ μ μ νν μ μλλ‘ νλ 컨νΈλ‘€
- μ¬μ© μ¬λ‘: μ¬μ©μμκ² μ ν μ΅μ μ μ 곡νκ³ μ ν λ μ¬μ©. κ΅κ° λͺ©λ‘, μΉ΄ν κ³ λ¦¬ μ ν, μ€μ λ± λ€μν μν©μμ νμ© κ°λ₯
π μ°Έμ‘° λ° λ νΌλ°μ€
- ARIA Authoring Practices: W3C ARIA Patters
- κ΄λ ¨ ν¨ν΄ λ° μ μ©λ μ κ·Όμ± κΈ°μ€ νμΈ
- κΈ°ν λ νΌλ°μ€: MUI, Ant Design λ±μ λ¬Έμμ λΉκ΅νμ¬ ν¨ν΄ λΆμ
π μ κ·Όμ± λ° ARIA μ μ© μ¬λΆ
- ARIA Pattern μ μ©: ARIA Pattern λ§ν¬
- μ¬μ©λ ARIA μμ±:
role="combobox",aria-controls,aria-expanded,aria-activedescendantλ±μ΄ μ¬μ©λ¨.role="combobox"λ‘ μμλ₯Ό 콀보λ°μ€λ‘ μλ³νκ³ ,aria-controlsλ‘ νμ μμλ₯Ό μ°Έμ‘°ν¨.aria-expandedλ νμ μνλ₯Ό λνλ΄κ³ ,aria-activedescendantλ νμ¬ ν¬μ»€μ€λ μ΅μ μ κ°λ¦¬ν΄
- μ¬μ©λ ARIA μμ±:
- μ κ·Όμ± κ΄λ ¨ κ³ λ €μ¬ν: ν€λ³΄λ λ΄λΉκ²μ΄μ
λ° μ€ν¬λ¦° 리λ μ¬μ©μ± κ³ λ €. μ΅μ
μ ν μ live regionμ ν΅ν΄ λ³κ²½μ¬νμ μλ΄νκ³ , μ νλ μ΅μ
μ
aria-selected="true"μ μ© - μ κ·Όμ± κΈ°λ₯ μμ½: 콀보λ°μ€μ νμ μ κ΄κ³λ₯Ό ARIA μμ±μΌλ‘ λͺ νν νκ³ , ν€λ³΄λλ‘ μ κ·Ό κ°λ₯νλλ‘ κ΅¬ν. μν λ³νλ₯Ό μ€ν¬λ¦° 리λμ μ리기 μν΄ live region νμ©
ποΈ λ§ν¬μ ꡬ쑰 λΆμ
- HTML μλ§¨ν± νκ·Έ μ¬μ©:
<select>μμ μ¬μ©. λ€μ΄ν°λΈ HTML μμλ₯Ό μ¬μ©νμ¬ μ½€λ³΄λ°μ€ μν μ λνλ - λ§ν¬μ ꡬ쑰 λΉκ΅:
// WAI-ARIA μμ
<div class="select">
<div id="exp_elem" role="combobox" aria-expanded="false">
<div role="textbox" aria-readonly="true"></div>
</div>
<ul role="listbox" aria-labelledby="exp_elem">
<li role="option"></li>
</ul>
</div>
// MUI
<div class="MuiSelect-root">
<div class="MuiSelect-select" aria-expanded="false">
<span>Selected option</span>
<input value="" />
</div>
<svg class="MuiSvgIcon-root"></svg>
</div>
// Ant Design
<div class="ant-select">
<div class="ant-select-selector">
<span class="ant-select-selection-item"></span>
<span class="ant-select-arrow">
<span class="ant-select-arrow-icon"></span>
</span>
</div>
</div>- ꡬ쑰μ μ ν μ΄μ :
- WAI-ARIA: ARIA μν κ³Ό μνλ₯Ό λͺ
μμ μΌλ‘ μ 곡νμ¬ μ κ·Όμ±μ ν보ν¨.
role="combobox"λ‘ μμμ μν μ λͺ νν νκ³ ,aria-expandedλ‘ νμ μν μ λ¬. μ΅μ μrole="listbox"λ΄λΆμrole="option"μΌλ‘ μ 곡 - MUI, Ant Design:
<div>μμλ₯Ό μ¬μ©νμ¬ μ½€λ³΄λ°μ€ ꡬ쑰λ₯Ό ꡬμ±ν¨. WAI-ARIAμ λΉν΄ λͺ μμ μΈ μν νμλ μ μ§λ§, ν΄λμ€λͺ μ ν΅ν΄ μλ―Έλ₯Ό νμ ν μ μμ. κΈ°λ³Έμ μΈ μ κ·Όμ±μ κ³ λ €νκ³ μμΌλ, μΆκ°μ μΈ ARIA μμ± μ μ©μ΄ νμν΄ λ³΄μ
- WAI-ARIA: ARIA μν κ³Ό μνλ₯Ό λͺ
μμ μΌλ‘ μ 곡νμ¬ μ κ·Όμ±μ ν보ν¨.
π‘ UI λΌμ΄λΈλ¬λ¦¬ λΉκ΅ λΆμ
- MUI:
<div>κΈ°λ° κ΅¬μ‘°. κΈ°λ³Έ μ κ·Όμ±μ κ³ λ €νκ³ μμΌλ ARIA μμ± μ μ©μ΄ λΆμ‘±ν΄ 보μ. ν΄λμ€λͺ μ ν΅ν΄ 콀보λ°μ€μ κ° λΆλΆμ μλ³ν μ μμ - Ant Design:
<div>κΈ°λ° κ΅¬μ‘°. ν΄λμ€λͺ μΌλ‘ 콀보λ°μ€μ κ° λΆλΆμ νμ. ARIA μμ± μ μ©μ΄ κ±°μ μμ΄ μ κ·Όμ± κ°μ μ΄ νμν΄ λ³΄μ
βοΈ κ΅¬ν λ° μ€κ³ κ³ λ €μ¬ν
- μ£Όμ μ€κ³ κ³ λ €μ¬ν: λ€μ΄ν°λΈ
<select>λλ<div>κΈ°λ° μ»€μ€ν 콀보λ°μ€ ꡬν μ, WAI-ARIA ν¨ν΄μ λ°λ₯Έ λ§ν¬μ κ³Ό ν€λ³΄λ μΈν°λμ ꡬνμ΄ μ€μν¨. μν λ³νλ₯Ό live regionμΌλ‘ μ리λ κ²λ μ κ·Όμ± ν₯μμ λμμ΄ λ¨ - μ½λ μμ:
<div class="select">
<div tabindex="0"
id="exp_elem"
role="combobox"
aria-labelledby="exp_label"
aria-expanded="false"
aria-controls="exp_elem_list"
aria-activedescendant=""
aria-required="true">
<span id="exp_label">Select an option</span>
<div role="textbox" aria-readonly="true"></div>
</div>
<ul id="exp_elem_list" role="listbox">
<li id="exp_elem_list_1" role="option">Option 1</li>
<li id="exp_elem_list_2" role="option">Option 2</li>
<li id="exp_elem_list_3" role="option">Option 3</li>
</ul>
<div aria-live="polite" aria-atomic="true" class="visually-hidden">
Option 1 selected
</div>
</div>// 콀보λ°μ€ ν κΈ ν¨μ
function toggleSelectExpanded(select) {
const expanded = select.getAttribute('aria-expanded') === 'true';
select.setAttribute('aria-expanded', !expanded);
}
// μ΅μ
μ ν ν¨μ
function selectOption(select, option) {
const selected = select.querySelector('[aria-selected="true"]');
if (selected) {
selected.setAttribute('aria-selected', 'false');
}
option.setAttribute('aria-selected', 'true');
select.querySelector('div[role="textbox"]').textContent = option.textContent;
select.setAttribute('aria-activedescendant', option.id);
// Live regionμΌλ‘ μ ν μ¬ν μλ¦Ό
const liveRegion = select.nextElementSibling;
liveRegion.textContent = option.textContent + ' selected';
}
// ν€λ³΄λ μ΄λ²€νΈ νΈλ€λ§
function handleKeyboard(event) {
const { key } = event;
const select = event.currentTarget;
const options = select.nextElementSibling.querySelectorAll('[role="option"]');
let activeIndex = Array.from(options).indexOf(document.getElementById(select.getAttribute('aria-activedescendant')));
if (key === 'ArrowDown') {
event.preventDefault();
activeIndex = Math.min(activeIndex + 1, options.length - 1);
selectOption(select, options[activeIndex]);
}
else if (key === 'ArrowUp') {
event.preventDefault();
activeIndex = Math.max(activeIndex - 1, 0);
selectOption(select, options[activeIndex]);
}
else if (key === 'Escape') {
select.querySelector('div[role="textbox"]').textContent = '';
select.setAttribute('aria-activedescendant', '');
toggleSelectExpanded(select);
}
}π ν μ€νΈ λ° κ²ν
- μ κ·Όμ± ν μ€νΈ: ν€λ³΄λ λ΄λΉκ²μ΄μ λ° μ€ν¬λ¦° 리λ νΈνμ± ν μ€νΈ. μ΅μ μ ν μ live regionμ ν΅ν΄ μ μ ν μλ΄κ° μ 곡λλμ§ νμΈ
- λΈλΌμ°μ νΈνμ± ν μ€νΈ: μ£Όμ λΈλΌμ°μ μμμ λ λλ§ λ° κΈ°λ₯ λμ νμΈ
- μ¬μ©μ νΌλλ°±: λ€μν μ¬μ©μλ₯Ό λμμΌλ‘ μ¬μ©μ± ν μ€νΈλ₯Ό μ§ννκ³ , νΌλλ°±μ μλ ΄νμ¬ κ°μ
π μΆκ° μ°Έκ³ μ¬ν
- κ΄λ ¨ λ¬Έμ: W3C WAI-ARIA Combobox Pattern, MDN -
<select>element - Web.dev Multi-Select Article
- μ견 λ° μ μ: Select μ»΄ν¬λνΈ κ΅¬ν μ μ κ·Όμ±μ κ³ λ €ν μ€κ³ λ° WAI-ARIA ν¨ν΄ μ μ©μ λν μ견 λ° μ μ
π― μν , μμ±, μν λ° νκ·Έ μμ±
- Role, Attribute, State
- Role:
role="combobox",role="textbox",role="listbox",role="option"μ¬μ©νμ¬ μ»΄ν¬λνΈμ μν λͺ μ - Attribute & State:
aria-expanded,aria-controls,aria-activedescendant,aria-selectedλ±μ μ¬μ©νμ¬ μ½€λ³΄λ°μ€μ μνμ κ΄κ³ ννaria-expanded: 콀보λ°μ€ νμ μν(μ΄λ¦Ό/λ«ν) νμaria-controls: 콀보λ°μ€κ° μ μ΄νλ νμ (listbox) μμ λͺ μaria-activedescendant: νμ¬ ν¬μ»€μ€λ μ΅μ μμμ ID μ°Έμ‘°aria-selected: μ νλ μ΅μ μμ λνλ
- Role:
- JavaScript λ° CSS μ¬μ© μ¬λΆ
- JavaScript μ¬μ©: ν€λ³΄λ μ΄λ²€νΈ νΈλ€λ§, νμ ν κΈ, μ΅μ μ ν λ±μ κΈ°λ₯ ꡬνμ μ¬μ©
- CSS μ μ©: 콀보λ°μ€μ λ μ΄μμ λ° λμμΈμ μν CSS μ€νμΌ μ μ©. λ¨, μκ°μ ν¨κ³Όκ° μ κ·Όμ±μ μ ν΄νμ§ μλλ‘ μ£Όμ