| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Nextjs
- 00000000
- 43인치모니터
- 이미지확장자
- 웹폰트최적화
- CSS
- iife
- WebP
- 반응형
- CSS헥사코드
- state끌어올리기
- 시멘틱마크업
- JS슬라이더
- hooks
- LS43FM701
- 즉시실행함수
- css 수직중앙정렬
- useState
- 수직중앙정렬
- 삼탠바이미
- webapi
- 리액트
- VITE
- 벤더프리픽스
- 컨테이너쿼리
- 상하중앙정렬
- 미디어쿼리
- vendorprefix
- react
- 스마트모니터
- Today
- Total
Nimbus
details 태그 (JS 없이 아코디언 만들기) 본문
FAQ나 상품 상세 정보 영역을 만들다 보면 아코디언 UI는 거의 빠지지 않고 등장한다.
보통은 이런 식으로 구현한다.
button.addEventListener('click', () => {
item.classList.toggle('is-open');
});
처음엔 간단하다.
그런데 "하나를 열면 나머지는 닫히게 해주세요", "키보드로도 동작해야 해요", "스크린리더에서 열림 상태를 읽어줘야 해요" 같은 요구사항이 붙기 시작하면 스크립트가 점점 길어진다. aria-expanded 값까지 직접 관리하다 보면 아코디언 하나에 생각보다 많은 손이 간다.
사실 HTML에는 이미 이 기능이 있다. 바로 <details>와 <summary>다.
오래된 태그라 "그거 기본 화살표 못생긴 그 태그 아니야?" 하고 넘기기 쉽지만, 최근 몇 가지 기능이 추가되면서 실무에서 충분히 쓸 만한 수준이 되었다. 이 글에서는 기본 사용법과 실제 적용 시 신경 써야 할 부분을 정리해 보려고 한다.
1. 기본 구조
<details> 안에 <summary>를 넣으면, summary가 클릭 영역이 되고 나머지 내용이 접히는 영역이 된다.
<details>
<summary>배송은 얼마나 걸리나요?</summary>
<p>결제 완료 후 평균 2~3일 정도 소요된다.</p>
</details>
스크립트 한 줄 없이 열고 닫기가 동작한다.
열린 상태는 open 속성으로 표시되기 때문에, 처음부터 펼쳐두고 싶다면 <details open>으로 작성하면 된다.
키보드 포커스, Enter/Space 동작, 열림·닫힘 상태 전달 같은 기본적인 접근성도 브라우저가 처리해 준다. JS로 만들 때 가장 놓치기 쉬운 부분을 기본으로 챙겨주는 셈이다. 다만 스크린리더와 OS 조합에 따라 안내 방식에 약간씩 차이는 있으니, 접근성 요구 수준이 높은 프로젝트라면 실제 스크린리더로 한 번 확인해 보는 것이 좋다.
2. 하나만 열리는 아코디언: name 속성
예전에 <details>를 실무에서 쓰기 애매했던 가장 큰 이유는 "하나를 열면 나머지가 닫히는" 동작을 만들 수 없었다는 점이다. 결국 이 부분 때문에 JS를 다시 붙이곤 했다.
지금은 name 속성으로 해결된다.
<details name="faq">
<summary>배송은 얼마나 걸리나요?</summary>
<p>결제 완료 후 평균 2~3일 정도 소요된다.</p>
</details>
<details name="faq">
<summary>교환은 어떻게 하나요?</summary>
<p>마이페이지 > 주문내역에서 신청할 수 있다.</p>
</details>
같은 name 값을 가진 details끼리 하나의 그룹이 되어, 하나를 열면 나머지는 자동으로 닫힌다. 라디오 버튼의 name과 같은 개념이라고 생각하면 이해가 쉽다.
크롬 120, 사파리 17.2, 파이어폭스 130부터 지원하며, 2024년 하반기부터 주요 브라우저 최신 버전에서는 모두 동작한다.
참고로 같은 그룹 안에서 여러 개에 open을 지정하면 소스 순서상 첫 번째 요소만 열린 상태로 표시된다. 기본으로 펼쳐둘 항목은 하나만 지정하는 것이 좋다.
3. 실무 적용 시 주의사항
기본 마커(화살표) 제거
디자인 시안대로 아이콘을 넣으려면 기본 삼각형 마커부터 없애야 한다. 사파리는 list-style: none만으로는 마커가 사라지지 않기 때문에 아래 두 가지를 같이 써주는 것이 안전하다.
summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}
커스텀 아이콘은 details[open] 선택자를 활용하면 열림 상태에 따라 쉽게 바꿀 수 있다.
summary::after {
content: '';
/* 아이콘 스타일 */
transition: transform 0.2s;
}
details[open] summary::after {
transform: rotate(180deg);
}
열고 닫힐 때 애니메이션
아마 가장 많이 부딪히는 부분일 것이다. 접히는 영역에 height transition을 걸어도 부드럽게 열리지 않는다. 이유는 두 가지다. 브라우저가 닫힌 상태의 콘텐츠를 content-visibility: hidden으로 즉시 숨겨버리고, CSS는 원래 0에서 auto로 가는 중간값을 계산하지 못한다.
최근에는 이를 CSS만으로 처리할 수 있는 방법이 생겼다.
:root {
interpolate-size: allow-keywords;
}
details::details-content {
block-size: 0;
overflow: hidden;
transition: block-size 0.3s, content-visibility 0.3s allow-discrete;
}
details[open]::details-content {
block-size: auto;
}
::details-content는 접히는 콘텐츠 영역을 선택하는 가상 요소로, 2025년 9월부터 크롬·사파리·파이어폭스 최신 버전에서 모두 지원된다. 덕분에 콘텐츠 영역을 꾸미기 위해 래퍼 div를 따로 둘 필요도 줄었다.
문제는 interpolate-size다. auto로의 transition을 허용해 주는 속성인데, 현재는 크롬·엣지 같은 크로미움 계열에서만 지원되고 사파리와 파이어폭스는 아직 지원하지 않는다. 즉 위 코드를 넣으면 크롬에서는 부드럽게 열리고, 사파리·파이어폭스에서는 애니메이션 없이 바로 열리고 닫힌다.
그래도 기능 자체가 깨지지는 않기 때문에 점진적 향상(Progressive Enhancement) 관점에서 미리 넣어두기 좋은 코드다. 모든 브라우저에서 동일한 애니메이션이 반드시 필요하다면 아직은 JS의 도움이 필요하다.
summary 안에 인터랙티브 요소 넣지 않기
summary 자체가 버튼 역할을 하기 때문에 그 안에 버튼이나 링크를 넣으면 클릭 동작이 겹치고, 스크린리더에서도 의도와 다르게 읽힐 수 있다. summary에는 제목 텍스트와 아이콘 정도만 두는 것이 좋다.
정리하면
1. 단순 아코디언/FAQ: 이제는 <details>만으로 충분하다. 기본적인 접근성도 함께 제공된다.
2. 하나만 열리는 아코디언: name 속성으로 JS 없이 구현할 수 있고, 주요 브라우저에서 모두 지원된다.
3. 애니메이션: ::details-content는 전 브라우저 지원, interpolate-size는 크로미움 전용이다. 사파리·파이어폭스에서는 애니메이션 없이 동작한다는 점을 감안해야 한다.
습관처럼 div와 클래스 토글로 아코디언을 만들어왔다면, 다음 작업에서는 <details>부터 먼저 떠올려 보는 것을 추천한다. 코드는 줄어들고, 기본 동작은 오히려 탄탄해진다.