라이트/다크 테마 모드 설정 (feat.ThemeProvider)

정태수·2025년 12월 4일
post-thumbnail

프로젝트에 처음으로 라이트/다크 모드 도입.

  1. 글로벌에 css 변수 생성 (generateCssVars)

  2. 글로벌에 주입 generateCssVars 결과를 :root, data-theme="dark" 에 병합

  3. 테마 제어

useAppTheme.ts

export default function useAppTheme(): ThemeTokens {
  const { resolvedTheme, theme } = useTheme()
  const current = resolvedTheme ?? theme ?? 'light'

  return useMemo(() => {
    return (current === 'dark' ? darkColors : lightColors) as ThemeTokens
  }, [current])
}

하위 컴포넌트에선 클라이언트 테마가 결정된 후 재렌더링 되면서 실제 테마를 반영.

하지만 부모는 초기 하이드레이션 시점에 (테마값이 정확하지 않은) 값이 고정되었음.

새로고침을 하면 자식은 dark를 유지, 부모 컴포넌트는 light 값 지정

따라서 화면 테마 설정이 깨져보였음

편하게 사용하려고(사실 기존 컨벤션이기도 함)

color: color.red01;
를 따라가기 위해 훅을 호출해서 컬러주입하는 방식
const appTheme = useAppTheme();

color: appTheme.red01;

이런 식으로 맞추려고 하다보니 훅을 호출했는데 부모컴포넌트에서 훅에서 사용하는 useTheme의 resolvedTheme이 undefined라 생기는 문제라고 볼수 있었다.

리터럴을 쓸 때 초기 렌더에서 inline으로 박혔다면, React가 부모를 재렌더하지 않으면 inline 값은 그대로 남습니다.
Emotion이 SSR 시 생성한 클래스에 리터럴 색이 들어가 있으면, 클라이언트에서 data-theme가 바뀌어도 그 CSS 규칙은 그대로 남아 보일 수 있음.

그래서 해결책은 CSS 변수를 쓰는 것이었음.

스타일에는 'var(--color-asset_default)' 같은 참조만 남는데 브라우저는 실제 변수 값을 런타임에 계산해서 적용. 그래서 [data-theme="dark"]에서 변수 값을 바꾸면 그 참조를 사용하는 모든 요소의 색이 즉시 변합니다 - 클래스 재생성 불필요

profile
프론트엔드 개발자

0개의 댓글