GODRICH

CSS 아코디언 애니메이션 9종 — JS 없이, CSS만으로

CSS 아코디언 애니메이션은 클릭한 항목만 펼쳐지고 나머지는 접힌 채로 남는 목록 UI로, 이 글은 JS 없이 CSS만으로 만드는 아홉 가지 방식을 모았습니다. 아홉 개 중 하나는 height 전환에 자바스크립트를 한 줄도 안 씁니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 인기 순이 아니라 아코디언이 실제로 겪는 순서로 놓았습니다. 기본 열고 닫기(01~02) 다음 시각 신호(03~05), 구조 확장(06~07), 마지막으로 강조와 재활용(08~09)입니다. 항목마다 필요한 규칙 조각만 골라 실었고, 완결본은 zip 안에 통째로 들어 있습니다.

01interpolate-size로 height:auto 전환

닫힌 패널이 interpolate-size 하나로 목표 높이까지 자연스럽게 늘어나며 열립니다. FAQ 목록이나 약관 상세처럼 내용 길이가 정해지지 않은 곳에 씁니다.

300ms느리게 600msallow-keywords
.panel {
  height: 0;
  overflow: hidden;
  interpolate-size: allow-keywords;
}
.accordion.is-open .panel {
  height: calc-size(auto, size);
  transition: height $duration $easing;
}
@supports not (height: calc-size(auto, size)) {
  .accordion.is-open .panel { height: auto; }
}

02name 속성으로 하나만 열림

다른 항목을 열면 열려 있던 항목이 자동으로 닫힙니다. 설정 메뉴나 한 번에 하나만 보여줄 FAQ에 씁니다.

name 속성닫힘 300ms라디오형
header.addEventListener('click', function () {
  var willOpen = !acc.classList.contains('is-open');
  acc.parentElement.querySelectorAll('.accordion').forEach(function (other) {
    other.classList.remove('is-open');
  });
  if (willOpen) acc.classList.add('is-open');
});

03화살표 마커 회전

열고 닫을 때 오른쪽 화살표가 180도 돌아가며 상태를 알려줍니다. 아코디언 헤더와 드롭다운 메뉴에 두루 씁니다.

180deg머리 오른쪽색 변화 추가
.accordion__chevron {
  transform: rotate(45deg);
  transition: transform $duration $easing;
}
.accordion.is-open .accordion__chevron {
  transform: rotate(-135deg);
}

04본문 페이드+슬라이드

본문이 위에서 살짝 밀려 내려오며 페이드인됩니다. 옵션 설명처럼 펼쳤을 때 시선을 끌어야 하는 내용에 씁니다.

8px200ms늦게 시작
.accordion__body {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity $duration $easing, transform $duration $easing;
}
.accordion.is-open .accordion__body {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 80ms;
}

05열린 항목 강조 테두리

열린 항목만 왼쪽 테두리와 배경이 강조됩니다. 지금 보고 있는 섹션을 표시해야 하는 설정 목록에 씁니다.

3px배경 포함색만
.accordion {
  border-left: 3px solid transparent;
  background: $card;
  transition: border-color $duration $easing, background $duration $easing;
}
.accordion.is-open {
  border-left-color: $color;
}

06중첩 아코디언

아코디언 안에 작은 아코디언이 들어 있어 두 단계로 펼쳐집니다. 다단계 FAQ나 카테고리 트리 메뉴에 씁니다.

들여쓰기 16px2단계동시 허용
.accordion__panel--inner {
  height: 0;
  overflow: hidden;
  interpolate-size: allow-keywords;
}
.accordion--inner.is-open .accordion__panel--inner {
  height: calc-size(auto, size);
  transition: height $duration $easing;
}

07calc-size() 폴백 패턴

calc-size()를 모르는 브라우저는 max-height 폴백으로 똑같이 움직입니다. 크로스 브라우저 지원이 필요한 폼에 씁니다.

@supportsmax-height 폴백auto 보간
@supports (height: calc-size(auto, size)) {
  .accordion.is-open .panel { height: calc-size(auto, size); }
}
@supports not (height: calc-size(auto, size)) {
  .panel { max-height: 0; transition: max-height $duration $easing; }
  .accordion.is-open .panel { max-height: 480px; }
}

08탄성 바운스로 열림

열릴 때 목표 높이를 살짝 넘었다가 튕기듯 자리를 잡습니다. 강조하고 싶은 프로모션 아코디언이나 온보딩 카드에 씁니다.

overshoot 6%cubic-bezier닫힘은 즉시
.accordion.is-open .panel {
  height: calc-size(auto, size);
  transition: height $duration $ease-pop;
}
.accordion.is-open .accordion__body {
  animation: bounce-in $duration $ease-pop;
}
@keyframes bounce-in {
  from { transform: scale(1.04); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

09사이드바 메뉴로 재활용

같은 아코디언 구조를 사이드바 메뉴의 하위 항목 접기·펼치기에 그대로 씁니다. 대시보드 사이드바나 문서 목차에 씁니다.

들여쓰기 12px아이콘 없이항상 하나 열림
.sidebar__list {
  height: 0;
  overflow: hidden;
  interpolate-size: allow-keywords;
  padding-left: 12px;
}
.sidebar.is-open .sidebar__list {
  height: calc-size(auto, size);
  transition: height $duration $easing;
}

어디서 깨지는가 — 함정 하나

아홉 개 전부 height를 직접 애니메이션하는데, 오랫동안 이건 금기였습니다. height를 바꾸면 브라우저가 매 프레임 레이아웃을 다시 계산하기 때문입니다. calc-size()와 interpolate-size는 이 계산을 브라우저가 미리 해 두고 전환 구간에서만 보간하는 방식이라 사정이 다릅니다. 다만 MDN의 interpolate-size 문서를 보면 이 값은 아직 일부 브라우저에만 있고, 나머지는 조용히 무시합니다. 그래서 07 항목처럼 @supports not (height: calc-size(auto, size))로 고정 max-height 폴백을 반드시 같이 넣어야 하는데, 이 폴백 없이 배포했다가 구형 브라우저에서 패널이 아예 안 열리는 걸 뒤늦게 발견했습니다.

폴백 말고 덧붙일 것이 하나 더 있습니다. max-height 폴백은 콘텐츠보다 넉넉하게 고정값을 잡아야 하는데, 너무 크면 닫히는 시간이 실제보다 길게 느껴집니다. 480px 안팎에서 시작해 콘텐츠 최대 높이에 맞춰 줄이는 걸 권하고, 이렇게 다듬은 값이 이미 반영된 아홉 개 파일의 압축 비밀번호는 문장 중간에 있는 34fn5mpm이며, 아래에서 zip으로 받을 수 있습니다.

접근성

운영체제의 동작 줄이기 옵션을 켠 사람에게는 아홉 개 모두 prefers-reduced-motion: reduce로 응답합니다. 열고 닫는 과정만 없애고 열림·닫힘이라는 결과 상태는 그대로 남기는 게 원칙입니다. 08 탄성 바운스는 오버슈트 없이 즉시 상태만 바뀌고, 04 페이드+슬라이드는 위치 이동 없이 투명도만 바뀝니다. prefers-reduced-motion 자체의 문법과 브라우저 지원은 MDN 문서를 참고하십시오.

@media (prefers-reduced-motion: reduce) {
  .accordion__panel, .accordion__chevron, .accordion__body {
    animation: none !important;
    transition: none !important;
  }
}

모달·드로어 같은 다른 전환 효과는 CSS 애니메이션 카테고리에서 찾을 수 있고, 사이트 운영 방향은 소개 페이지에 적어 두었습니다.

FAQ

CSS 아코디언 애니메이션에 꼭 JS가 필요합니까?

여는 동작 자체는 네이티브 <details>나 체크박스 트릭으로 JS 없이도 가능합니다. 다만 02처럼 세 개 이상을 배타적으로 묶거나 06처럼 중첩 상태를 관리하려면 짧은 JS 한두 줄을 곁들이는 편이 안전합니다.

calc-size()는 어느 브라우저에서 됩니까?

이 글을 쓰는 시점 기준 지원이 갈리므로 실제 프로젝트에 쓰기 전에 caniuse.com에서 최신 지원표를 직접 확인하고, 07 항목의 @supports 폴백을 항상 같이 배포하십시오.

아홉 개를 한 페이지에 다 넣어도 됩니까?

넣을 수는 있지만 한 화면에는 한두 종류가 좋습니다. 08 탄성 바운스와 05 테두리 강조는 "이 아코디언은 특별하다"를 말하는 용도라 페이지 전체에 하나씩만 쓰는 걸 권합니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.