본문으로 이동geul.io

CSS height auto 애니메이션, 아코디언 여닫는 2가지 방법

올린이 geul날짜 조회 1
  • 아코디언이나 접히는 메뉴를 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이 돌지 않고, 열림 상태가 바뀔 때만 움직임

좋아요저장

댓글

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.