GODRICH

CSS 다크모드 토글 애니메이션 9종 — 한 줄 복붙으로

CSS 다크모드 토글 애니메이션 아홉 개를 한 번에 모았습니다. 아이콘이 바뀌는 가장 흔한 표현부터, 원 하나로 화면을 덮는 화려한 전환, 라이트·다크·자동까지 다루는 3단 스위치까지 담았고 전부 transform과 opacity만 움직입니다.

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

아홉 개는 인기순이 아니라 얼마나 화려한가 순으로 놓았습니다. 아이콘 하나 바뀌는 가장 단순한 표현(01~02)에서 시작해, 화면 전체를 건드리는 전환(03~06)을 지나, 색만 은은히 바뀌거나 세 가지 상태까지 다루는 것(07~09)으로 끝납니다. 아래 코드 블록엔 각 토글의 핵심 규칙만 남겼고, 붙여 넣으면 바로 돌아가는 완결본은 zip 안에 따로 있습니다.

01해↔달 아이콘 전환

토글을 누르면 해 아이콘이 회전하며 사라지고 달 아이콘이 반대쪽에서 돌아 들어옵니다. 헤더의 다크모드 버튼처럼 아이콘 하나로 상태를 보여줘야 하는 자리에 씁니다.

400ms회전 180°크로스페이드
.toggle__sun, .toggle__moon {
  position: absolute; inset: 5px; border-radius: 50%;
  transform-origin: center;
}
.toggle__sun  { opacity: 1; transform: rotate(0deg); }
.toggle__moon { opacity: 0; transform: rotate(-90deg); }

.toggle[data-dark="true"] .toggle__sun  { opacity: 0; transform: rotate(90deg); }
.toggle[data-dark="true"] .toggle__moon { opacity: 1; transform: rotate(0deg); }

02노브 슬라이드

스위치 트랙 위 노브가 좌우로 미끄러지고, 트랙 배경은 두 겹을 겹쳐 색이 함께 바뀝니다. 설정 화면처럼 켜고 끄는 느낌이 분명해야 하는 토글에 씁니다.

28px 트랙느리게 600ms그림자 추가
.knob {
  position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--color);
  transition: transform var(--duration) var(--easing);
}
.track[data-dark="true"] .knob { transform: translateX(20px); }

03클릭점 원형 확산

토글을 누른 자리에서 원이 scale(0)에서 scale(1)로 퍼지며 화면을 다크모드 색으로 덮습니다. 랜딩 히어로처럼 전환 자체가 인상적이어야 하는 토글 하나에 씁니다.

중앙에서1.2배속사각 확산
.wipe {
  position: absolute; top: -60px; right: -60px;
  width: 320px; height: 320px; border-radius: 50%;
  background: $color; transform-origin: 78% 22%;
  transform: scale(0);
}
.panel[data-dark="true"] .wipe { transform: scale(1); }

04light-dark() 한 줄 전환

color-scheme 값 하나만 라이트·다크로 바꾸면 light-dark()로 선언한 배경·글자색이 한 번에 뒤집힙니다. 컴포넌트 라이브러리나 디자인 시스템 토큰처럼 값 하나로 여러 요소를 한꺼번에 바꿔야 하는 곳에 씁니다.

변수 2개전환 300ms테두리 포함
:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

.card {
  background: light-dark($subject-cream, $stage-ink);
  color: light-dark($subject-ink, $subject-cream);
}

05월식 마스크 전환

달 모양 마스크가 옆에서 밀려와 해 아이콘을 가리며 다크모드로 넘어갑니다. 프로필 메뉴처럼 브랜드다운 표현이 필요한 토글에 씁니다.

500ms그림자 진하게역방향 재생
.mask {
  position: absolute; inset: 2px; border-radius: 50%;
  background: $color;
  transform: translateX(-60px);
  transition: transform var(--duration) var(--easing);
}
.wrap[data-dark="true"] .mask { transform: translateX(4px); }

06카드 뒤집기 전환

카드가 Y축으로 계속 돌며 앞면(라이트, 해)과 뒷면(다크, 달)이 번갈아 보입니다. 카드형 설정 스위치나 프로필 메뉴처럼 이미 카드 모양인 UI에 자연스럽게 얹습니다.

600msperspective 800px그림자 따라 이동
.card__inner {
  transform-style: preserve-3d;
  transition: transform var(--duration) var(--easing);
}
.card[data-dark="true"] .card__inner { transform: rotateY(180deg); }
.card__face { backface-visibility: hidden; }
.card__face--back { transform: rotateY(180deg); }

07아이콘 모핑

해 아이콘의 광선 여섯 개가 하나씩 접혀 사라지고, 중심 원은 두 겹을 겹쳐 라이트에서 다크 색으로 바뀝니다. 툴바처럼 작은 아이콘 버튼 하나에 씁니다.

SVG path350mseasing 스냅
.ray {
  transform-origin: 50% 17px;
  transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(1);
  transition: opacity var(--duration) var(--easing), transform var(--duration) var(--easing);
}
.icon[data-dark="true"] .ray {
  opacity: 0;
  transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(0);
}

08전체 색 페이드 전환

배경과 글자색이 동시에 부드럽게 뒤바뀝니다. 토글 컨트롤 없이 페이지 전체가 테마를 바꾸는 문서 뷰어 같은 화면에 씁니다.

800ms필터 blur 2px즉시 전환
.light, .dark {
  position: absolute; inset: 0;
  transition: opacity var(--duration) var(--easing);
}
.light { opacity: 1; }
.dark  { opacity: 0; }
.panel[data-dark="true"] .light { opacity: 0; }
.panel[data-dark="true"] .dark  { opacity: 1; }

09라이트·다크·자동 3단

세 칸짜리 트랙 위 인디케이터가 라이트→다크→자동 순서로 이동합니다. 데스크톱 앱 환경설정처럼 자동(시스템 따라가기)까지 골라야 하는 화면에 씁니다.

3단 트랙350ms인디케이터 그림자
.thumb {
  position: absolute; top: 3px; left: 3px;
  width: 26px; height: 22px; border-radius: 999px;
  background: var(--color);
  transition: transform var(--duration) var(--easing);
}
.track[data-mode="dark"] .thumb { transform: translateX(32px); }
.track[data-mode="auto"] .thumb { transform: translateX(64px); }

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

아홉 개가 하나같이 transformopacity 밖으로 나가지 않는 데는 분명한 이유가 있습니다. background-color를 직접 애니메이션하면 브라우저마다 색 보간 방식이 달라 중간 색이 탁하게 흐려지지만, 두 레이어를 겹쳐 opacity로 크로스페이드하면 각 레이어의 색은 그대로 유지됩니다. MDN의 애니메이션 성능 문서transform·opacity를 권하는 이유이기도 합니다. 03의 실제 프로덕션 버전은 클릭 지점에서 원이 퍼지는 걸 View Transitions API로 구현하지만, 이 API는 Safari·Firefox 지원이 Chrome보다 늦게 들어와 브라우저마다 결과가 달라집니다 — 그래서 이 데모는 순수 CSS scale로 같은 느낌만 근사했고, 실제 프로젝트에서 API를 쓸 땐 폴백을 반드시 두십시오.

함정은 아니지만 짚어둘 점이 두 가지 있습니다. 04의 light-dark()color-scheme이 요소에 명시돼 있어야 작동합니다. 부모 어딘가에서 color-scheme을 안 정하면 값이 늘 라이트로 고정돼 "안 바뀐다"는 버그 리포트를 받게 됩니다. 이 값들이 이미 정리된 아홉 개 파일의 압축 비밀번호는 kjdwbr3q이고, 아래 버튼으로 받을 수 있습니다.

다른 하나는 탭·키보드 조작입니다. 01·05·07처럼 버튼 하나로 만든 토글은 :focus-visible에 링 표시를 넣어야 하고, 09처럼 여러 칸으로 된 것은 방향키로 칸을 옮길 수 있게 role="radiogroup"aria-checked를 붙였습니다.

접근성

시스템 설정에서 "동작 줄이기"를 켠 사람에게는 아홉 개 토글 모두 prefers-reduced-motion: reduce 블록이 먼저 반응합니다. 움직임은 끄되 상태는 남기는 게 원칙이라, 전환 애니메이션만 제거하고 라이트·다크 상태 자체는 그대로 보여줍니다. 03의 원 확산과 06의 카드 뒤집기도 예외 없이 즉시 상태만 바뀝니다. 브라우저별 지원 여부는 MDN의 prefers-reduced-motion 문서에 정리돼 있습니다.

@media (prefers-reduced-motion: reduce) {
  .toggle__sun, .toggle__moon, .knob, .wipe, .mask, .card__inner, .ray, .thumb {
    animation: none !important;
    transition: none !important;
  }
}

다른 버튼·전환 주제는 CSS 애니메이션 카테고리에 모여 있고, 이 사이트가 무엇을 다루는지는 소개에 있습니다.

FAQ

다크모드 토글에는 이 아홉 개 중 무엇을 골라야 하나요?

헤더처럼 눈에 자주 띄는 자리는 01 해↔달이나 02 노브 슬라이드처럼 소박한 걸 고르십시오. 03 원형 확산이나 06 카드 뒤집기는 랜딩 페이지처럼 인상을 남겨야 하는 자리 하나에만 씁니다.

light-dark()는 지금 바로 써도 되나요?

caniuse.com에서 지원 범위를 먼저 확인하십시오. Chrome·Edge·Safari 최신판은 지원하지만 오래된 브라우저를 아직 지원해야 한다면 prefers-color-scheme 미디어 쿼리로 폴백을 남겨야 합니다.

React에서는 클래스 토글 대신 뭘 쓰나요?

zip 안 react/ 폴더의 컴포넌트는 다크 여부를 props 하나로 받습니다. 01~08은 dark boolean, 09는 mode("light"·"dark"·"auto")입니다. SCSS 모듈은 var()data-dark·data-mode 속성만 읽으므로 변수 세 개와 상태값만 넘기면 됩니다.

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

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