GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

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

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

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

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

01해↔달 아이콘 전환

어떤 움직임인가

토글을 누르면 해 아이콘이 회전하며 사라지고 달 아이콘이 반대쪽에서 돌아 들어옵니다.

해↔달 아이콘 전환
새 창에서 열기

01 · 해↔달 아이콘 전환 — 측정 누적면적 26.961% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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노브 슬라이드

무엇이 보이나

스위치 트랙 위 노브가 좌우로 미끄러지고, 트랙 배경은 두 겹을 겹쳐 색이 함께 바뀝니다.

노브 슬라이드
새 창에서 열기

02 · 노브 슬라이드 — 측정 누적면적 26.944% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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)로 퍼지며 화면을 다크모드 색으로 덮습니다. 랜딩 히어로처럼 전환 자체가 인상적이어야 하는 토글 하나에 씁니다.

클릭점 원형 확산(View Transition)
새 창에서 열기

03 · 클릭점 원형 확산(View Transition) — 측정 누적면적 26.663% · 움직인 프레임 19/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

중앙에서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); }

04라이트·다크 한 번에 뒤집기

어떤 움직임인가

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

라이트·다크 한 번에 뒤집기
새 창에서 열기

04 · 라이트·다크 한 번에 뒤집기 — 측정 누적면적 26.944% · 움직인 프레임 14/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

변수 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월식 마스크 전환

무엇이 보이나

달 모양 마스크가 옆에서 밀려와 해 아이콘을 가리며 다크모드로 넘어갑니다.

월식 마스크 전환
새 창에서 열기

05 · 월식 마스크 전환 — 측정 누적면적 26.797% · 움직인 프레임 11/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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축으로 계속 돌며 앞면(라이트, 해)과 뒷면(다크, 달)이 번갈아 보입니다.

카드 뒤집기 전환
새 창에서 열기

06 · 카드 뒤집기 전환 — 측정 누적면적 22.674% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

600msperspective 800px그림자 따라 이동

쓰는 자리

카드형 설정 스위치나 프로필 메뉴처럼 이미 카드 모양인 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아이콘 모핑

어떤 움직임인가

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

아이콘 모핑
새 창에서 열기

07 · 아이콘 모핑 — 측정 누적면적 0.483% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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전체 색 페이드 전환

무엇이 보이나

배경과 글자색이 동시에 부드럽게 뒤바뀝니다.

전체 색 페이드 전환
새 창에서 열기

08 · 전체 색 페이드 전환 — 측정 누적면적 26.961% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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단
새 창에서 열기

09 · 라이트·다크·자동 3단 — 측정 누적면적 1.517% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

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); }

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

색을 직접 애니메이션하지 않는 이유

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

light-dark 와 color-scheme

함정은 아니지만 짚어둘 점이 두 가지 있습니다. 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 애니메이션 효과와 전환 주제는 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 속성만 읽으므로 변수 세 개와 상태값만 넘기면 됩니다.

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

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