Theming in React

하려고 하는 것.

ThemeProvider로 theme 상태를 저장 및 Component tree가 theme 상태에 접근할 수 있게 하고 useTheme 훅을 통해서 theme상태를 토글 또는 설정하는 함수를 사용하려고 한다.

// entry tsx

<ThemeProvider>
  <App />
</ThemeProvider>


// component tsx

const { toggleTheme } = useTheme()

const handleClick = () => { toggleTheme() }

CSS Variables

theming을 위해서 root에 css variable을 설정해준다.

:root {
  --foreground: #000;
  --background: #fff;
}

.dark {
  --foreground: #fff;
  --background: #000;
}

Class based theming

dark 클래스가 적용되면 dark theme color가 적용 된다. class를 통해서 css variable 설정을 바꿔준다.

<html class="light">

<!-- or --> 

<html class="dark">

Persistent theme state

localStorage에 theme 상태를 저장해서 theme을 유지할 수 있도록 한다.

// at last

window.localStorage.set("theme", "light") // or
window.localStorage.set("theme", "dark")

ThemeProvider

ThemeProvider는 ThemeContext를 children tree에 전달하는 역할을 하는 컴포넌트다. ThemeProvider의 children tree는 ThemeContext에 access할 수 있다.

ThemeContext는 theme 상태와 상태와 관련된 로직을 가지고 있다.

type ColorTheme = "light" | "dark";
type TheemContextType = {
  theme: ColorTheme;
  setTheme: (theme: ColorTheme) => void;
  toggleTheme: () => void;
};

export const ThemeContext = createContext<TheemContextType | null>(null);

export const ThemeProvider = ({children}: PropsWithChildren) => {
  // ...
  
  return <ThemeContext.Provider>{children}</ThemeContext.Provider>
}

default theme

기본 theme은 로컬 스토리지에 저장된 theme을 사용한다. 만약 값이 없다면 system preference의 theme으로 설정해준다. 이를 위한 helper 함수를 만든다.

const isServer = () => typeof window === "undefined";
const getPersistedTheme = (): ColorTheme => {
  if (isServer()) {
    return "light";
  }
  const value = window.localStorage.getItem(KEY);
  if (!value) {
    const systemTheme = getSystemTheme();
    window.localStorage.setItem(KEY, systemTheme);
    return systemTheme;
  }
  return value as ColorTheme;
};
const getSystemTheme = (): ColorTheme => {
  const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  return isDark ? "dark" : "light";
};

setTheme

setTheme은 다음 세 가지 행동을 해야 한다.

  1. change react state
  2. change html class name
  3. change localStorage’value

References

https://github.com/pacocoursey/next-themes/blob/main/packages/next-themes/src/index.tsx