Skip to content

SelectΒ #4

@hsskey

Description

@hsskey

πŸ“‹ μ»΄ν¬λ„ŒνŠΈ 정보

  • μ»΄ν¬λ„ŒνŠΈ 이름: 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 μŠ€νƒ€μΌ 적용. 단, μ‹œκ°μ  νš¨κ³Όκ°€ 접근성을 μ €ν•΄ν•˜μ§€ μ•Šλ„λ‘ 주의

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions