React, shadcn/ui 다크모드 적용 및 깜빡임 문제

윤태현·2025년 3월 22일
0

REACT

목록 보기
21/21
post-thumbnail

Reactshadcn/ui를 사용해서 다크모드를 적용해보려고 합니다.

shadcn/ui를 선택한 이유는 공식 문서에 나온 대로 간단한 설치만으로 필요한 CSS와 컴포넌트 구조가 자동으로 설정되기 때문입니다.
이를 통해 별도의 스타일링 작업 없이도 바로 사용 가능하며 TailwindCSS와 자연스럽게 결합되어 있어 커스터마이징도 용이하기 때문에 사용했습니다.

언어 : TypeScript (v5.7.2)
라이브러리 : React (v19.0.0)
스타일 : TailwindCSS (v4.0.15)
빌드 도구: Vite (v6.2.0)



1. Shadcn/UI 설치

shadcn/ui 설치 공식 홈페이지

저는 base color를 Neutral 사용했습니다.



2. 다크모드 적용

shadcn/ui 다크모드 적용 공식 홈페이지

공식 홈페이지에서 Vite를 사용해서 다크모드 적용을 했습니다.

다른 부분을 작성하자면 storageKeywebui-theme로 진행했습니다.
로컬스토리지에 저장되는 키 값이기 때문에 원하는 값으로 작성하시면 됩니다.

// ThemeProvider.tsx
export function ThemeProvider({ children, defaultTheme = "system", storageKey = "webui-theme", ...props }: ThemeProviderProps) {
  ...
}
// App.tsx
export default function App() {
  ...
  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider defaultTheme="dark" storageKey="webui-theme">
        <RouterProvider router={router} />
      </ThemeProvider>
    </QueryClientProvider>
  )
}



3. 페이지 새로고침 시 깜빡임

워낙 짧은 프레임이어서 녹화에는 잘 보이진 않지만 다크모드로 적용 후 새로고침을 할 때마다 화면 깜빡임 현상이 발생합니다.


ThemeProvider로 적용하면 웹 페이지는 아래와 같이 html 태그에 class="dark"가 적용됩니다.


브라우저 렌더링 과정

React에서 새로고침할 때 처음에는 라이트 모드(하얀 화면)로 보였다가 이후 다크 모드로 변경되는 현상은 아래의 과정 중에서 발생합니다.

  1. DOM 트리 생성

    • HTML이 처음 파싱되어 DOM이 생성
    • <html> 태그가 먼저 렌더링되지만 class="dark"가 아직 적용되지 않은 상태
  2. 스타일 규칙 생성

    • CSS가 파싱되지만 dark 모드가 localStorage에서 불러오기 전까지 기본 값(예: light)이 적용
    • 따라서 초기 화면은 기본 스타일 light 모드
  3. 렌더 트리 생성 (③) → 레이아웃 (④)

    • 초기에 라이트 모드 스타일이 적용된 상태로 렌더 트리가 생성
  4. useEffect 실행 → 다크 모드 적용

    • ThemeProvider.tsx에서 useEffectlocalStorage에서 저장된 테마 값을 읽고 class="dark"<html> 태그에 추가합니다.
    • 하지만 이 시점에서는 이미 라이트 모드 스타일로 화면이 렌더링된 상태
  5. 페인트(리페인트) (⑤) → 깜빡임 발생

    • class="dark"가 적용되면서 스타일이 변경됩니다.
    • 변경된 스타일을 적용하기 위해 리페인트(Repaint) 과정이 발생하며, 이때 화면이 잠깐 깜빡임

결론
깜빡임 현상은 초기 렌더링에서 다크 모드 스타일이 적용되지 않은 상태로 DOM이 생성된 후 React에서 상태를 변경하면서 리페인트가 발생하기 때문

이를 방지하려면, HTML이 처음 로드될 때부터 class="dark"를 적용하도록 index.html에서 테마를 먼저 설정해야 합니다.



해결

// index.html

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Ollama WebUi</title>

    <script type="text/javascript">
      (function () {
        const isClient = typeof window !== "undefined";
        if (isClient) {
          const storageTheme = localStorage.getItem("webui-theme");
          let theme = "light";

          if (storageTheme === "system") {
            theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
          } else if (storageTheme) {
            theme = storageTheme;
          }

          document.documentElement.classList.add(theme);
          document.documentElement.style.backgroundColor = theme === "dark" ? "#0a0a0a" : "#FFFFFF";
        }
      })();
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

React가 실행되기 전에 index.html<head>에서 스크립트를 추가하여 초기 테마를 설정했습니다.

  • localStorage에서 저장된 테마 값을 확인하여 html 태그에 class="dark" 또는 class="light"를 즉시 추가

  • 시스템 테마(prefers-color-scheme: dark)를 따르는 경우 이를 반영하여 테마를 결정

  • document.documentElement.style.backgroundColor를 설정하여 초기 렌더링 시 배경색이 올바르게 적용

적용 후 새로고침할 때마다 렌더링 전 theme가 미리 적용되어 깜빡임 현상을 해결할 수 있었습니다.

0개의 댓글