CSS height auto 애니메이션, 아코디언 여닫는 2가지 방법
아코디언이나 접히는 메뉴를
height: 0에서height: auto로 바꾸면 애니메이션 없이 툭 열림. auto는 숫자가 아니라서 브라우저가 중간 높이를 못 만듦방법 1: 감싸는 요소를 grid로 만들고
grid-template-rows를0fr에서1fr로 바꿈. Chrome, Edge, Safari, Firefox 모두 부드럽게 열림방법 2:
<details>에interpolate-size: allow-keywords를 주면height: auto그대로 애니메이션됨. JavaScript가 필요 없지만, 2026년 10월 기준 부드럽게 열리는 브라우저는 Chrome과 Edge뿐두 방법 모두 CSS 데모로 유명한 jh3yy(Jhey Tompkins)가 사이드바와 아코디언 데모에서 쓴 방법임. 아래 예제는 Chrome 155에서 높이 변화를 직접 재어 확인함
height: auto 트랜지션이 안 되는 이유
transition: height 0.3s가 움직이려면 시작 높이와 끝 높이를 숫자로 알아야 함auto는 "내용만큼"이라는 약속일 뿐 숫자가 아님. 그래서 브라우저가 중간 단계를 건너뛰고 끝 상태를 바로 그림그동안 쓰던 우회법은 둘 다 아쉬움
JavaScript로
scrollHeight를 재서 px 값을 넣음. 내용이 바뀔 때마다 다시 재야 함max-height: 1000px처럼 넉넉한 값으로 애니메이션함. 실제 높이와 차이가 클수록 닫을 때 한참 멈칫하다 움직임
방법 1: grid 0fr에서 1fr로 (모든 브라우저)
jh3yy가 2025년 10월 사이드바 데모에 올린 원본 코드는 이것임.
li > div[inert] { grid-template-rows: 0fr; }
li > div {
grid-template-rows: 1fr;
transition: grid-template-rows .26s;
}fr는 grid가 남는 공간을 나눠 주는 단위임. 행 하나짜리 grid에서1fr이면 행이 내용 높이만큼 커지고,0fr이면 0이 됨fr값은 숫자라서0fr과1fr사이를 브라우저가 채울 수 있음. 그 결과 내용 높이까지 부드럽게 늘어남높이를 재는 코드가 없으니 안의 글이 늘거나 줄어도 알아서 맞음
바로 붙여 쓰는 예제
<button class="trigger" aria-expanded="false" aria-controls="shipping">배송 안내</button>
<div class="panel" id="shipping" inert>
<div class="panel-inner">
<div class="panel-body">평일 오후 2시 전에 주문하면 다음 날 도착합니다.</div>
</div>
</div>.panel {
display: grid;
grid-template-rows: 1fr;
transition: grid-template-rows 0.3s ease;
}
.panel[inert] {
grid-template-rows: 0fr;
}
.panel-inner {
overflow: hidden;
}
.panel-body {
padding: 16px;
}const button = document.querySelector('.trigger');
const panel = document.getElementById('shipping');
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
panel.inert = open;
});요소를 세 겹으로 둔 이유
.panel: 행 높이를0fr과1fr사이로 바꾸는 바깥 틀.panel-inner: 행 밖으로 넘친 내용을 잘라 내는 칸. grid 안의 자식은 이 하나뿐이어야 함.panel-body: 여백(padding)을 주는 칸
JavaScript는 열림 상태를 바꾸는 데만 씀. 움직임은 CSS가 맡음
inert는 요소를 없는 것처럼 만드는 HTML 속성임닫힌 패널 속 링크로 Tab 키가 들어가거나 스크린 리더가 읽는 일을 막음
jh3yy 원본도 열림 여부를
inert로 구분함. 클래스 대신 쓰면 접근성까지 한 번에 챙김
방법 2: details와 interpolate-size (JavaScript 없음)
jh3yy가 2025년 10월 애플 홈페이지 스타일 펼침 메뉴 데모에 올린 원본 코드는 이것임.
::details-content {
interpolate-size: allow-keywords;
transition: content-visibility, height;
transition-behavior: allow-discrete;
}
[open]::details-content { height: fit-content; }<details>와<summary>는 누르면 열리고 닫히는 HTML 기본 요소임. 여닫기는 브라우저가 알아서 함::details-content는<details>안에서<summary>를 뺀 본문 부분을 고르는 선택자임interpolate-size: allow-keywords를 주면auto,fit-content같은 낱말 높이도 애니메이션할 수 있음content-visibility는 닫힌 본문을 숨기는 속성인데, 원래는 단번에 바뀜.allow-discrete를 붙이면 닫히는 움직임이 끝날 때까지 기다렸다가 숨김
바로 붙여 쓰는 예제
<details name="faq">
<summary>배송은 얼마나 걸리나요</summary>
<p>평일 오후 2시 전에 주문하면 다음 날 도착합니다.</p>
</details>
<details name="faq">
<summary>교환은 어떻게 하나요</summary>
<p>받은 날부터 7일 안에 신청하면 됩니다.</p>
</details>:root {
interpolate-size: allow-keywords;
}
details::details-content {
height: 0;
overflow: clip;
transition:
height 0.3s ease,
content-visibility 0.3s allow-discrete;
}
details[open]::details-content {
height: auto;
}interpolate-size는 자식 요소에 물려짐.:root에 한 번 적으면 페이지 전체에 적용됨닫힌 상태에
height: 0을 적어 둬야 0부터 늘어남overflow: clip이 없으면 늘어나는 동안 글이 칸 밖으로 삐져나옴name이 같은<details>끼리는 하나를 열면 나머지가 저절로 닫힘. 자주 묻는 질문 목록에 알맞음닫혀 있는 본문도 페이지 찾기(Ctrl+F)로 찾으면 저절로 열림 (Chrome, Edge, Firefox 148부터)
Safari와 Firefox에서는
2026년 10월 기준 Safari와 Firefox는
interpolate-size를 지원하지 않음 (MDN 호환성 자료)그래도 고장 나지는 않음. 애니메이션 없이 바로 열리고 닫힐 뿐임
jh3yy는 2025년 11월 "모든 브라우저에서 돈다"며 올린 아코디언 데모에서,
interpolate-size가 없는 브라우저는 페이지를 열 때 높이를 한 번 재서 쓰게 메움
어떤 방법을 고를까
grid 0fr에서 1fr | details와 interpolate-size | max-height 꼼수 | |
|---|---|---|---|
부드럽게 열리는 브라우저 | Chrome, Edge, Safari, Firefox | Chrome, Edge (나머지는 바로 열림) | 모든 브라우저 |
JavaScript | 상태 바꾸는 몇 줄 | 없음 | 상태 바꾸는 몇 줄 |
높이 숫자 | 필요 없음 | 필요 없음 | 넉넉한 값을 짐작해 적음 |
닫힐 때 움직임 | 고름 | 고름 | 멈칫한 뒤 움직임 |
FAQ처럼 단순한 아코디언은 방법 2가 가장 짧음. Safari 사용자는 애니메이션만 빠짐
사이드바, 내비게이션처럼 모든 브라우저에서 같은 움직임을 보여야 하면 방법 1
max-height꼼수는 새로 만드는 화면이라면 쓸 이유가 없음
놓치기 쉬운 점
안쪽 칸에
overflow: hidden이 꼭 있어야 함 (방법 1)없으면
0fr이어도 접히지 않음. grid 칸은 기본적으로 내용보다 작아지지 않기 때문임Chrome에서 재어 보니 두 줄짜리 내용이
0fr에서도 38px 그대로 남았음
padding은 잘라 내는 칸보다 한 겹 더 안쪽에 줌 (방법 1)
overflow: hidden칸에 padding 16px을 주면 닫혀도 위아래 합쳐 32px 띠가 남음 (12px로 재어 보니 24px이 남았음)바깥
.panel에 padding을 줘도 똑같이 남음
calc-size()는 계산이 필요할 때만 씀 (방법 2)height: calc-size(auto, size + 20px)처럼 auto 높이에 값을 더하거나 뺄 수 있는 함수임jh3yy의 2024년 7월 첫 아코디언 데모는 이 함수를 썼다가, 9월에
interpolate-size로 바꿔 다시 올림지원 범위는
interpolate-size와 같음
동작 줄이기 설정을 켠 사람에게는 애니메이션을 끔
운영체제의 "동작 줄이기"는 CSS의
prefers-reduced-motion으로 알 수 있음
@media (prefers-reduced-motion: reduce) {
.panel,
details::details-content {
transition: none;
}
}브라우저 지원
기능 | Chrome, Edge | Safari | Firefox |
|---|---|---|---|
grid-template-rows 애니메이션 (방법 1) | 107 | 16 | 66 |
inert 속성 (방법 1) | 102 | 15.5 | 112 |
::details-content (방법 2) | 131 | 18.4 | 143 |
interpolate-size, calc-size() (방법 2) | 129 | 미지원 | 미지원 |
details name, 하나만 열기 (방법 2) | 120 | 17.2 | 130 |
2026년 10월 8일 MDN 호환성 자료 기준
방법 1에 필요한 기능은 2023년 4월 Firefox 112를 마지막으로 주요 브라우저가 모두 지원함
자주 묻는 질문
max-height로 하면 안 되나요?
되긴 함. 다만
max-height: 1000px인데 실제 높이가 200px이면, 닫을 때 800px 구간은 겉으로 아무 변화 없이 시간만 흐름. 누르고 한참 뒤에 닫히는 것처럼 보임
React나 Vue에서도 쓸 수 있나요?
CSS만으로 움직이는 방법이라 프레임워크와 상관없음. 열림 상태에 따라
inert나open만 바꿔 주면 됨
처음부터 열린 채로 뜰 때도 애니메이션되나요?
아님. 페이지가 처음 그려질 때는 transition이 돌지 않고, 열림 상태가 바뀔 때만 움직임