CSS 아코디언 메뉴 만들기, 체크박스 해킹 원리

아코디언 메뉴는 자바스크립트 없이 순수 CSS만으로 만들 수 있습니다. 숨겨진 체크박스(또는 라디오) input과 label의 클릭 감지, 그리고 :checked 형제 선택자만 있으면 클릭할 때마다 열리고 닫히는 동작이 구현됩니다.
스크립트 삽입이 막힌 환경에서 접이식 메뉴가 필요한 사람, 혹은 왜 이 코드가 이렇게 동작하는지 원리를 확인하려는 사람에게 필요한 내용을 코드 레벨에서 정리합니다.
요약 ① 숨겨진 input[type=checkbox|radio]와 label[for=]의 클릭 감지가 열림 상태를 저장하는 역할을 합니다. ②
:checked형제 선택자가 header 배경색과 content의 max-height를 바꿔 여닫히는 것처럼 보이게 만듭니다. ③ checkbox는 항목마다 독립적으로 열리고, radio는 name을 공유해야 한 번에 하나만 열립니다.
왜 자바스크립트 없는 아코디언이 필요한가
접이식 메뉴나 FAQ, 상세정보 접기 UI가 필요한데 자바스크립트를 쓸 수 없는 상황이 실제로 있습니다. 티스토리나 워드프레스 같은 블로그 플랫폼의 HTML 편집 모드는 <script> 태그가 걸러지거나 실행되지 않는 경우가 많습니다.
사내 게시판이나 CMS도 보안 정책상 스크립트 삽입을 막아두는 경우가 흔합니다.
이럴 때는 클릭 이벤트를 감지하는 JS 코드 대신, HTML과 CSS만으로 "클릭하면 상태가 바뀌는" 동작을 구현해야 합니다.
순수 CSS 아코디언이 바로 이 문제를 푸는 방식이고, 별도 라이브러리나 스크립트 로딩 없이 페이지가 가벼워진다는 부수 효과도 있습니다.
체크박스/라디오 해킹의 작동 원리 (직접 코드로 만드는 법)
이 기법의 핵심은 input의 체크 상태를 자바스크립트가 아니라 CSS 선택자로 읽는 것입니다. 순서대로 뜯어보면 이렇습니다.
1단계 — 숨겨진 input과 label을 짝짓는다
실제로 클릭을 받는 요소는 눈에 보이지 않는 input이 아니라 label입니다. label의 for 속성값과 input의 id가 같으면, label을 클릭했을 때 브라우저가 자동으로 그 input을 체크/해제합니다.
이건 HTML의 기본 동작이지 아코디언만의 특별한 기능이 아닙니다.
<input type="checkbox" id="acc-item-0" class="accordion-input" />
<label for="acc-item-0" class="accordion-header">제목</label>
<div class="accordion-content">
<div class="accordion-inner"><p>내용</p></div>
</div>
2단계 — input을 화면에서 지우되 기능은 살려둔다
display: none을 쓰면 일부 브라우저에서 클릭·포커스 동작까지 같이 죽는 경우가 있어, 보통 아래처럼 위치와 투명도로만 숨깁니다.
.accordion-input {
position: absolute;
opacity: 0;
z-index: -1;
}
input은 여전히 DOM에 존재하고 체크 상태도 바뀌지만, 화면에는 보이지 않습니다. label만 보이는 헤더처럼 렌더링됩니다.
3단계 — :checked 형제 선택자로 상태를 읽는다
여기가 자바스크립트를 대신하는 지점입니다. input이 체크되면 바로 뒤에 오는 형제 요소의 스타일을 바꿀 수 있습니다.
.accordion-input:checked + .accordion-header {
background: #e5e7eb;
}
.accordion-input:checked ~ .accordion-content {
max-height: 500px;
}
+는 바로 다음 형제(header) 하나만 고르고, ~는 그 뒤에 오는 모든 형제(content 포함) 중 조건에 맞는 것을 고릅니다.
input이 header와 content보다 반드시 앞에 와야 이 선택자들이 작동합니다.
4단계 — max-height와 transition으로 여닫히는 느낌을 만든다
CSS 트랜지션은 height: auto를 직접 애니메이션할 수 없기 때문에, 닫힌 상태는 max-height: 0, 열린 상태는 실제 내용보다 넉넉히 큰 고정값(예: 500px)으로 잡고 그 사이를 트랜지션합니다.
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
저희 도구로 확인하기.
다중/단일 개방, 색상, 속도를 조정하면 미리보기와 HTML·CSS 코드가 실시간으로 갱신됩니다. 가입이나 설치 없이 브라우저에서 바로 복사할 수 있습니다.

checkbox와 radio, 그리고 자주 나는 실수
체크박스 모드와 라디오 모드는 input의 type 한 글자만 다르지만 결과는 다릅니다. 코드 레벨에서 무엇이 이 차이를 만드는지 확인해야 할 지점이 세 가지 있습니다.
| 구분 | checkbox 모드 | radio 모드 |
|---|---|---|
| 동시에 열리는 항목 수 | 여러 개 가능 (다중 개방) | 최대 1개 (단일 개방) |
| name 속성 | 있어도 동작에 영향 없음 | 반드시 모든 항목이 같은 값을 공유해야 함 |
| 항목을 다시 클릭하면 | 닫힘(체크 해제) | 닫히지 않음(다른 항목을 눌러야 닫힘) |
세 가지 모두 브라우저가 checkbox와 radio를 원래 다르게 취급하기 때문에 생기는 차이이지, 아코디언 코드가 별도로 분기 처리를 하는 게 아닙니다.
radio는 태생적으로 같은 name 그룹 안에서 하나만 선택되도록 설계된 input이라, 그 성질을 그대로 빌려온 것입니다.
실제 코드를 읽을 때 확인해야 할 실수는 아래 세 가지입니다.
- 라디오 모드인데 name이 항목마다 다르다. name이 겹치지 않으면 브라우저가 서로 다른 그룹으로 인식해 여러 항목이 동시에 열립니다. "단일 개방"을 의도했다면 모든 input의 name이 정확히 같은 문자열인지부터 확인해야 합니다.
- max-height를 실제 내용보다 작게 잡았다. 위에서 본 대로 이 기법은
height: auto를 쓸 수 없어 고정 max-height로 대체합니다. 이미지가 들어가거나 텍스트가 긴 항목의 실제 높이가 지정한 max-height(예: 500px)를 넘으면, 넘는 부분은 스크롤도 없이 그대로 잘려서 보이지 않습니다. - label의 for와 input의 id가 어긋난다. 항목을 추가·삭제하거나 코드를 직접 편집하다가 id 번호가 밀리면, label을 클릭해도 다른 input을 가리키거나 아무 input도 가리키지 않아 클릭이 먹지 않는 것처럼 보입니다. 헤더를 눌러도 반응이 없다면 가장 먼저 for와 id 쌍부터 맞춰봐야 합니다.
정리
- 아코디언의 열림·닫힘은 자바스크립트가 아니라 숨겨진 input의 체크 상태와 CSS
:checked형제 선택자가 만듭니다. - label의
for와 input의id가 정확히 일치해야 클릭이 input에 전달됩니다. - checkbox는 항목별 독립 개방, radio는 name을 공유해야 하는 단일 개방이라는 점이 코드상 차이의 전부입니다.
- max-height는 실제 콘텐츠보다 넉넉해야 하며, 부족하면 내용이 잘립니다.
- 값을 바꿔가며 결과를 바로 확인하려면 미리보기와 코드를 함께 보여주는 도구를 쓰는 편이 손으로 값을 하나씩 바꿔보는 것보다 빠릅니다.
다중/단일 개방, 색상, 속도를 조정하면 미리보기와 HTML·CSS 코드가 실시간으로 갱신됩니다. 가입이나 설치 없이 브라우저에서 바로 복사할 수 있습니다.
자주 묻는 질문
정말 자바스크립트 없이 작동하나요?
네, 숨겨진 checkbox·radio input과 label, 그리고 CSS의 :checked 형제 선택자만으로 동작합니다.
클릭을 감지하는 이벤트 리스너나 상태를 저장하는 스크립트가 전혀 필요하지 않고, 체크 상태 자체가 브라우저가 관리하는 값입니다.
항목을 여러 개 동시에 열리게 하려면 어떻게 하나요?
input의 type을 radio가 아니라 checkbox로 바꾸면 됩니다. checkbox는 항목마다 독립적으로 체크·해제되기 때문에 여러 항목이 동시에 열린 상태를 유지할 수 있습니다.
name 속성이 겹쳐도 checkbox 모드에서는 열림 개수에 영향을 주지 않습니다.
화살표 아이콘 방향이 안 바뀌어요
화살표는 .accordion-input:checked + .accordion-header::after 선택자로 회전합니다. +는 input의 바로 다음 형제만 고릅니다.
header가 input 바로 뒤에 있지 않도록 마크업 순서를 바꾸면 이 선택자가 더 이상 걸리지 않아 화살표가 그대로 멈춰 있게 됩니다.
워드프레스나 티스토리에도 이 코드를 그대로 쓸 수 있나요?
네, HTML 편집 모드에서 생성된 HTML과 CSS를 함께 붙여넣으면 대부분의 블로그 플랫폼에서 동일하게 작동합니다.
자바스크립트를 쓰지 않는 방식이라 스크립트 삽입이 막힌 편집기에서도 그대로 렌더링됩니다.
