๐ ์ปดํฌ๋ํธ ์ ๋ณด
- ์ปดํฌ๋ํธ ์ด๋ฆ: 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๋ ํ์ฌ ํฌ์ปค์ค๋ ์ต์
์ ๊ฐ๋ฆฌํด
- ์ ๊ทผ์ฑ ๊ด๋ จ ๊ณ ๋ ค์ฌํญ: ํค๋ณด๋ ๋ด๋น๊ฒ์ด์
๋ฐ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์ฑ ๊ณ ๋ ค. ์ต์
์ ํ ์ 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 ์์ฑ ์ ์ฉ์ด ํ์ํด ๋ณด์
๐ก 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์ ํตํด ์ ์ ํ ์๋ด๊ฐ ์ ๊ณต๋๋์ง ํ์ธ
- ๋ธ๋ผ์ฐ์ ํธํ์ฑ ํ
์คํธ: ์ฃผ์ ๋ธ๋ผ์ฐ์ ์์์ ๋ ๋๋ง ๋ฐ ๊ธฐ๋ฅ ๋์ ํ์ธ
- ์ฌ์ฉ์ ํผ๋๋ฐฑ: ๋ค์ํ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ์ฌ์ฉ์ฑ ํ
์คํธ๋ฅผ ์งํํ๊ณ , ํผ๋๋ฐฑ์ ์๋ ดํ์ฌ ๊ฐ์
๐ ์ถ๊ฐ ์ฐธ๊ณ ์ฌํญ
๐ฏ ์ญํ , ์์ฑ, ์ํ ๋ฐ ํ๊ทธ ์์ฑ
- 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: ์ ํ๋ ์ต์
์์ ๋ํ๋
- JavaScript ๋ฐ CSS ์ฌ์ฉ ์ฌ๋ถ
- JavaScript ์ฌ์ฉ: ํค๋ณด๋ ์ด๋ฒคํธ ํธ๋ค๋ง, ํ์
ํ ๊ธ, ์ต์
์ ํ ๋ฑ์ ๊ธฐ๋ฅ ๊ตฌํ์ ์ฌ์ฉ
- CSS ์ ์ฉ: ์ฝค๋ณด๋ฐ์ค์ ๋ ์ด์์ ๋ฐ ๋์์ธ์ ์ํ CSS ์คํ์ผ ์ ์ฉ. ๋จ, ์๊ฐ์ ํจ๊ณผ๊ฐ ์ ๊ทผ์ฑ์ ์ ํดํ์ง ์๋๋ก ์ฃผ์
๐ ์ปดํฌ๋ํธ ์ ๋ณด
๐ ์ฐธ์กฐ ๋ฐ ๋ ํผ๋ฐ์ค
๐ ์ ๊ทผ์ฑ ๋ฐ ARIA ์ ์ฉ ์ฌ๋ถ
role="combobox",aria-controls,aria-expanded,aria-activedescendant๋ฑ์ด ์ฌ์ฉ๋จ.role="combobox"๋ก ์์๋ฅผ ์ฝค๋ณด๋ฐ์ค๋ก ์๋ณํ๊ณ ,aria-controls๋ก ํ์ ์์๋ฅผ ์ฐธ์กฐํจ.aria-expanded๋ ํ์ ์ํ๋ฅผ ๋ํ๋ด๊ณ ,aria-activedescendant๋ ํ์ฌ ํฌ์ปค์ค๋ ์ต์ ์ ๊ฐ๋ฆฌํดaria-selected="true"์ ์ฉ๐๏ธ ๋งํฌ์ ๊ตฌ์กฐ ๋ถ์
<select>์์ ์ฌ์ฉ. ๋ค์ดํฐ๋ธ HTML ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฝค๋ณด๋ฐ์ค ์ญํ ์ ๋ํ๋role="combobox"๋ก ์์์ ์ญํ ์ ๋ช ํํ ํ๊ณ ,aria-expanded๋ก ํ์ ์ํ ์ ๋ฌ. ์ต์ ์role="listbox"๋ด๋ถ์role="option"์ผ๋ก ์ ๊ณต<div>์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฝค๋ณด๋ฐ์ค ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฑํจ. WAI-ARIA์ ๋นํด ๋ช ์์ ์ธ ์ญํ ํ์๋ ์ ์ง๋ง, ํด๋์ค๋ช ์ ํตํด ์๋ฏธ๋ฅผ ํ์ ํ ์ ์์. ๊ธฐ๋ณธ์ ์ธ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ๊ณ ์์ผ๋, ์ถ๊ฐ์ ์ธ ARIA ์์ฑ ์ ์ฉ์ด ํ์ํด ๋ณด์๐ก UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋น๊ต ๋ถ์
<div>๊ธฐ๋ฐ ๊ตฌ์กฐ. ๊ธฐ๋ณธ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ๊ณ ์์ผ๋ ARIA ์์ฑ ์ ์ฉ์ด ๋ถ์กฑํด ๋ณด์. ํด๋์ค๋ช ์ ํตํด ์ฝค๋ณด๋ฐ์ค์ ๊ฐ ๋ถ๋ถ์ ์๋ณํ ์ ์์<div>๊ธฐ๋ฐ ๊ตฌ์กฐ. ํด๋์ค๋ช ์ผ๋ก ์ฝค๋ณด๋ฐ์ค์ ๊ฐ ๋ถ๋ถ์ ํ์. ARIA ์์ฑ ์ ์ฉ์ด ๊ฑฐ์ ์์ด ์ ๊ทผ์ฑ ๊ฐ์ ์ด ํ์ํด ๋ณด์โ๏ธ ๊ตฌํ ๋ฐ ์ค๊ณ ๊ณ ๋ ค์ฌํญ
<select>๋๋<div>๊ธฐ๋ฐ ์ปค์คํ ์ฝค๋ณด๋ฐ์ค ๊ตฌํ ์, WAI-ARIA ํจํด์ ๋ฐ๋ฅธ ๋งํฌ์ ๊ณผ ํค๋ณด๋ ์ธํฐ๋์ ๊ตฌํ์ด ์ค์ํจ. ์ํ ๋ณํ๋ฅผ live region์ผ๋ก ์๋ฆฌ๋ ๊ฒ๋ ์ ๊ทผ์ฑ ํฅ์์ ๋์์ด ๋จ๐ ํ ์คํธ ๋ฐ ๊ฒํ
๐ ์ถ๊ฐ ์ฐธ๊ณ ์ฌํญ
<select>element๐ฏ ์ญํ , ์์ฑ, ์ํ ๋ฐ ํ๊ทธ ์์ฑ
role="combobox",role="textbox",role="listbox",role="option"์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ์ ์ญํ ๋ช ์aria-expanded,aria-controls,aria-activedescendant,aria-selected๋ฑ์ ์ฌ์ฉํ์ฌ ์ฝค๋ณด๋ฐ์ค์ ์ํ์ ๊ด๊ณ ํํaria-expanded: ์ฝค๋ณด๋ฐ์ค ํ์ ์ํ(์ด๋ฆผ/๋ซํ) ํ์aria-controls: ์ฝค๋ณด๋ฐ์ค๊ฐ ์ ์ดํ๋ ํ์ (listbox) ์์ ๋ช ์aria-activedescendant: ํ์ฌ ํฌ์ปค์ค๋ ์ต์ ์์์ ID ์ฐธ์กฐaria-selected: ์ ํ๋ ์ต์ ์์ ๋ํ๋