본문으로 건너뛰기
개발 머꼬
개발 노트HTML
hohyeon.dev14

아코디언을 만들려고 쓴 JavaScript를 details의 name 속성으로 지운 과정

  • #Accessibility
  • #Engineering Note
  • #HTML

문제 발생

FAQ 아코디언에서 "하나를 열면 나머지는 닫힌다"를 구현하려고 <details>에 이벤트를 걸어 서로를 닫고 있었습니다.

const panels = document.querySelectorAll(".faq details");
panels.forEach((panel) => {
  panel.addEventListener("toggle", () => {
    if (!panel.open) return;
    panels.forEach((other) => { if (other !== panel) other.open = false; });
  });
});

동작은 했지만 서버 렌더링 직후 JavaScript가 붙기 전까지는 여러 개가 열려 있을 수 있었고, 목록이 동적으로 늘면 이벤트를 다시 걸어야 했습니다.

원인 분석

<details>는 열고 닫는 동작 자체는 브라우저가 해주지만, 여러 개 사이의 배타 관계는 표현할 방법이 없었습니다. 그래서 그 관계만을 위해 스크립트가 필요했습니다.

name 속성이 이 관계를 마크업으로 표현합니다. 같은 name 값을 가진 <details>들은 한 그룹이 되고, 그중 하나만 열릴 수 있습니다. 하나를 열면 브라우저가 다른 하나를 닫습니다. 라디오 버튼의 name과 같은 발상입니다.

해결 방안

  1. 같은 그룹에 같은 name을 줍니다. 위의 스크립트가 통째로 사라집니다.
<details name="faq">
  <summary>배송은 얼마나 걸리나요?</summary>
  <p>보통 2~3일 걸립니다.</p>
</details>
<details name="faq">
  <summary>교환할 수 있나요?</summary>
  <p>수령 후 7일 이내에 가능합니다.</p>
</details>
  1. 초기 상태는 open 하나만 씁니다. 그룹 안에서 여러 개에 open을 달면 문서 순서상 첫 번째만 열린 채로 렌더됩니다. 두 개를 열어두려던 의도라면 그 자체가 배타 그룹과 모순입니다.
  2. 상태 변화는 toggle 이벤트로 받습니다. 상태가 바뀐 뒤에 발생하므로, 핸들러 안에서는 details.open이 이미 새 값입니다.
details.addEventListener("toggle", () => {
  if (details.open) trackOpen(details.querySelector("summary").textContent);
});
  1. name의 지원 범위는 <details> 자체보다 좁습니다. <details>는 2020년 1월부터 널리 쓸 수 있지만 name은 나중에 들어온 속성입니다. 미지원 브라우저에서는 배타 동작만 사라지고 각 항목은 정상적으로 열고 닫힙니다 — 기능이 깨지는 게 아니라 하나 덜 하는 쪽으로 무너지므로, 점진적 향상으로 쓰기 좋습니다.
  2. 열린 영역을 애니메이션하려면 ::details-content를 봅니다. 예전에는 내용을 별도 요소로 감싸야 했습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.