CSS 다크모드 토글 애니메이션 9종 — 한 줄 복붙으로
CSS 다크모드 토글 애니메이션 아홉 개를 한 번에 모았습니다. 아이콘이 바뀌는 가장 흔한 표현부터, 원 하나로 화면을 덮는 화려한 전환, 라이트·다크·자동까지 다루는 3단 스위치까지 담았고 전부 transform과 opacity만 움직입니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 해↔달 아이콘 전환
- 02 노브 슬라이드
- 03 클릭점 원형 확산(View Transition)
- 04 light-dark() 한 줄 전환
- 05 월식 마스크 전환
- 06 카드 뒤집기 전환
- 07 아이콘 모핑
- 08 전체 색 페이드 전환
- 09 라이트·다크·자동 3단
아홉 개는 인기순이 아니라 얼마나 화려한가 순으로 놓았습니다. 아이콘 하나 바뀌는 가장 단순한 표현(01~02)에서 시작해, 화면 전체를 건드리는 전환(03~06)을 지나, 색만 은은히 바뀌거나 세 가지 상태까지 다루는 것(07~09)으로 끝납니다. 아래 코드 블록엔 각 토글의 핵심 규칙만 남겼고, 붙여 넣으면 바로 돌아가는 완결본은 zip 안에 따로 있습니다.
01해↔달 아이콘 전환
토글을 누르면 해 아이콘이 회전하며 사라지고 달 아이콘이 반대쪽에서 돌아 들어옵니다. 헤더의 다크모드 버튼처럼 아이콘 하나로 상태를 보여줘야 하는 자리에 씁니다.
.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노브 슬라이드
스위치 트랙 위 노브가 좌우로 미끄러지고, 트랙 배경은 두 겹을 겹쳐 색이 함께 바뀝니다. 설정 화면처럼 켜고 끄는 느낌이 분명해야 하는 토글에 씁니다.
.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)로 퍼지며 화면을 다크모드 색으로 덮습니다. 랜딩 히어로처럼 전환 자체가 인상적이어야 하는 토글 하나에 씁니다.
.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()로 선언한 배경·글자색이 한 번에 뒤집힙니다. 컴포넌트 라이브러리나 디자인 시스템 토큰처럼 값 하나로 여러 요소를 한꺼번에 바꿔야 하는 곳에 씁니다.
: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월식 마스크 전환
달 모양 마스크가 옆에서 밀려와 해 아이콘을 가리며 다크모드로 넘어갑니다. 프로필 메뉴처럼 브랜드다운 표현이 필요한 토글에 씁니다.
.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에 자연스럽게 얹습니다.
.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아이콘 모핑
해 아이콘의 광선 여섯 개가 하나씩 접혀 사라지고, 중심 원은 두 겹을 겹쳐 라이트에서 다크 색으로 바뀝니다. 툴바처럼 작은 아이콘 버튼 하나에 씁니다.
.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전체 색 페이드 전환
배경과 글자색이 동시에 부드럽게 뒤바뀝니다. 토글 컨트롤 없이 페이지 전체가 테마를 바꾸는 문서 뷰어 같은 화면에 씁니다.
.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단
세 칸짜리 트랙 위 인디케이터가 라이트→다크→자동 순서로 이동합니다. 데스크톱 앱 환경설정처럼 자동(시스템 따라가기)까지 골라야 하는 화면에 씁니다.
.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); }
어디서 깨지는가 — 함정 하나
아홉 개가 하나같이 transform과 opacity 밖으로 나가지 않는 데는 분명한 이유가 있습니다. 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 속성만 읽으므로 변수 세 개와 상태값만 넘기면 됩니다.