ARIA 접근성 가이드

컴포넌트 패턴

디스클로저(펼치기/접기)

버튼 하나로 관련 내용을 펼치고 접는 패턴입니다. 버튼의 aria-expanded로 상태를 알리고, 접힌 내용은 hidden으로 숨깁니다. 가장 간단한 대안은 네이티브 <details>입니다.

언제 쓰나요

디스클로저(disclosure)는 버튼 하나가 내용 하나를 펼치고 접는 가장 단순한 표시/숨김 패턴입니다. 자주 묻는 질문의 답, “자세히 보기”로 감춘 부가 설명, 고급 검색 옵션, 사이트 내비게이션의 하위 메뉴 목록처럼 처음에는 감춰 두어도 되는 내용에 씁니다.

  • 먼저 네이티브 <details>·<summary>를 검토하세요. 펼침 상태 안내, 키보드 조작, 스크립트 없이도 동작하는 점까지 브라우저가 제공합니다. 다만 <summary> 안에 제목 요소를 넣으면 제목 탐색에서의 표현이 브라우저마다 다르고, 스타일 제약이 있습니다.
  • 디스클로저 여러 개를 묶어 제목 목록처럼 보여 주면 아코디언입니다.
  • 열린 내용이 다른 내용 위에 겹쳐 뜨고 바깥을 누르면 닫혀야 한다면 팝업 계열(메뉴 버튼, 대화상자, 콤보박스)을 검토합니다.
  • 사이트의 상단 내비게이션 드롭다운은 대부분 role="menu"가 아니라 디스클로저로 만드는 것이 맞습니다. 펼쳐지는 것은 명령 목록이 아니라 링크 목록이기 때문입니다.

꼭 필요한 정보는 숨기지 마세요

가격, 오류 안내, 필수 입력 조건처럼 모든 사용자가 반드시 봐야 하는 내용은 접어 두지 않습니다. 접힌 내용은 화면낭독기의 가상 커서로도 읽히지 않으므로, 존재 자체를 놓칠 수 있습니다.

구조

버튼과 패널의 관계
<button aria-expanded="false" aria-controls="p1">배송 안내</button>   ← 상태는 버튼에
<div id="p1" hidden>…</div>                                         ← 패널은 실제로 숨김
구조
요소역할속성설명
펼침 버튼buttonaria-expanded, (선택) aria-controls펼침 상태는 버튼이 갖습니다.
패널특별한 역할 없음id, hidden접힘 상태에서는 hidden(또는 display:none)으로 접근성 트리에서도 빠집니다.

버튼이 콘텐츠 구역의 제목 역할을 한다면 <h3><button>…</button></h3>처럼 제목 요소 안에 버튼을 넣어 제목 탐색으로도 찾을 수 있게 합니다. 반대로 버튼을 감싸지 않고 제목 요소 자체에 role="button"을 주면 제목 의미가 사라집니다.

키보드 동작

키보드 동작
키동작
Tab펼침 버튼으로 포커스를 옮깁니다. 펼쳐진 패널 안의 링크·버튼은 그 다음 순서가 됩니다.
Enter패널을 펼치거나 접습니다.
Space패널을 펼치거나 접습니다.

펼치거나 접어도 포커스는 버튼에 그대로 남습니다. 패널 안으로 포커스를 강제로 옮기지 않습니다. 네이티브 <button>을 쓰면 위 동작은 모두 기본으로 제공됩니다.

필요한 ARIA

button
펼침 컨트롤은 버튼입니다. <button type="button">을 쓰면 역할을 따로 줄 필요가 없습니다. 링크(<a href="#">)로 만들면 “링크”로 안내되어 페이지 이동으로 오해하게 됩니다.
aria-expanded
버튼이 제어하는 패널이 펼쳐졌으면 true, 접혔으면 false입니다. 화면낭독기는 이 값으로 “확장됨/축소됨”을 안내합니다. 처음부터 "false"를 명시해야 “펼칠 수 있는 버튼”으로 인식됩니다. 패널에 붙이지 않고 버튼에 붙입니다.
aria-controls (선택)
버튼이 어떤 패널을 제어하는지 id로 연결합니다. APG에서 선택 사항이며, 이 값을 활용하는 보조기술은 일부(예: JAWS)입니다. 스크립트에서 패널을 찾는 기준으로 쓰기 좋아 함께 넣는 경우가 많습니다. 패널이 DOM에 없을 때는 존재하지 않는 id를 가리키지 않도록 합니다.

숨기는 방법은 hidden으로

접힌 패널은 hidden 속성이나 display:none으로 숨깁니다. aria-hidden="true"만 주거나 height:0·opacity:0으로만 감추면 안의 링크가 여전히 Tab으로 잡혀 “보이지 않는 곳에 포커스가 가는” 문제가 생깁니다. 페이지 내 검색(Ctrl + F)으로 접힌 내용을 찾게 하고 싶다면 hidden="until-found"와 beforematch 이벤트를 검토할 수 있습니다(브라우저 지원 확인 필요). 자세한 내용은 콘텐츠 숨기기를 참고하세요.

직접 해 보기

두 구현을 마우스와 키보드(Tab, Enter, Space)로 각각 조작해 보세요.

잘못된 구현

배송 안내

클릭 이벤트만 붙인 div입니다. 포커스를 받을 수 없고, 버튼이라는 사실도 펼침 상태도 전달되지 않습니다. ▾ 아이콘의 방향은 화면을 보는 사람에게만 의미가 있습니다.

올바른 구현

제목 안의 네이티브 버튼에 aria-expanded를 두고, 패널은 hidden으로 숨깁니다. 아이콘 방향도 aria-expanded 값으로 그립니다.

네이티브 details 요소

교환·반품 안내

상품을 받은 날부터 7일 안에 신청할 수 있습니다.

<summary>를 누르면 브라우저가 open 속성을 스스로 넣고 뺍니다. 스크립트가 전혀 필요 없고, 화면낭독기도 펼침 상태를 안내합니다.

전체 코드

HTML
<h3>
  <button type="button" aria-expanded="false" aria-controls="ship-panel">
    <span class="icon" aria-hidden="true"></span>배송 안내
  </button>
</h3>
<div id="ship-panel" hidden>
  <p>평일 오후 2시 이전 주문은 당일 출고하며, 보통 1~2일 안에 도착합니다.</p>
</div>

<script>
  document.querySelectorAll('button[aria-expanded][aria-controls]').forEach(initDisclosure);
</script>
JavaScript
// 디스클로저: 버튼의 aria-expanded와 패널의 hidden을 항상 함께 바꿉니다.
function initDisclosure(button) {
  var panel = document.getElementById(button.getAttribute('aria-controls'));
  function set(open) {
    button.setAttribute('aria-expanded', String(open));
    panel.hidden = !open;
  }
  // 네이티브 <button>이므로 click 하나로 마우스·Enter·Space를 모두 처리합니다.
  button.addEventListener('click', function () {
    set(button.getAttribute('aria-expanded') !== 'true');
  });
  set(button.getAttribute('aria-expanded') === 'true');   // 초기 상태 맞추기
}
CSS — 상태에 맞춘 아이콘
/* 아이콘 방향을 상태에서 바로 읽습니다 */
button[aria-expanded] .icon::before { content: "▸"; display: inline-block; margin-right: .4em; }
button[aria-expanded="true"] .icon::before { transform: rotate(90deg); }
HTML — 네이티브 대안
<details>
  <summary>교환·반품 안내</summary>
  <p>상품을 받은 날부터 7일 안에 신청할 수 있습니다.</p>
</details>
비권장비권장 — div, 패널에 aria-expanded, 높이로만 숨김
<div class="toggle" onclick="toggle()">배송 안내 ▾</div>
<div class="panel" aria-expanded="false" style="height:0;overflow:hidden">…</div>
권장권장 — 버튼에 aria-expanded, hidden으로 숨김
<button type="button" aria-expanded="false" aria-controls="p1">배송 안내</button>
<div id="p1" hidden>…</div>

화면낭독기에서는

화면낭독기 안내 예시 (표현은 화면낭독기·브라우저·버전에 따라 조금씩 다릅니다)
상황들리는 안내 (예)
접힌 버튼에 포커스배송 안내, 버튼, 축소됨
Enter로 펼침확장됨
펼친 뒤 아래 화살표로 읽기평일 오후 2시 이전 주문은 …
제목 탐색(H)으로 이동배송 안내, 버튼, 축소됨, 제목 수준 3
클릭만 붙인 div를 가상 커서로 읽을 때배송 안내 (버튼·상태 안내 없음)
<details>의 <summary>에 포커스교환·반품 안내, 버튼, 축소됨 (브라우저에 따라 “요약” 등으로 안내)

점검 체크리스트

  • 펼침 컨트롤이 <button>(또는 <summary>)이고, Tab·Enter·Space로 조작됩니다.
  • aria-expanded는 패널이 아니라 버튼에 있고, 처음부터 "true"/"false"로 명시되어 있습니다.
  • 화면에 보이는 펼침 상태와 aria-expanded 값이 항상 일치합니다.
  • 접힌 패널은 hidden/display:none으로 숨겨져, 안의 링크가 Tab으로 잡히지 않습니다.
  • 펼친 패널이 DOM 순서상 버튼 바로 뒤에 있어, 펼친 뒤 Tab을 누르면 패널 안으로 자연스럽게 이어집니다.
  • 펼치고 접을 때 포커스가 버튼에 그대로 남습니다.
  • 화살표 아이콘은 aria-hidden="true"이거나 CSS로만 그려져 이름에 섞이지 않습니다.
  • 제목 역할을 하는 버튼은 제목 요소 안에 있습니다.

관련 WCAG 성공 기준

참고 자료