Theming
Vapor UI의 테마 시스템을 설정하고 커스터마이징하는 방법을 알아보세요.Theming
Vapor UI는 라이트 모드, 다크 모드, 그리고 시스템 설정에 동기화되는 테마를 손쉽게 지원합니다. 이 가이드를 통해 ThemeProvider를 설정하고 useTheme 훅을 사용하여 테마를 관리하는 방법을 알아보세요.
시작하기
1. 패키지 설치
먼저, Vapor UI 핵심 패키지를 설치해야 합니다.
npm install @vapor-ui/core@beta2. ThemeProvider 설정
애플리케이션의 최상위(root)를 ThemeProvider로 감싸고, 필요한 스타일을 import 합니다.
import { ThemeProvider } from '@vapor-ui/core';
import '@vapor-ui/core/dist/styles.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko" suppressHydrationWarning>
<body>
<ThemeProvider defaultTheme="system">{children}</ThemeProvider>
</body>
</html>
);
}Next.js와 같은 서버 사이드 렌더링(SSR) 환경에서는 suppressHydrationWarning을 <html> 태그에 추가하는 것을 권장합니다. 이는 서버에서 렌더링한 테마와 클라이언트의 테마가 달라 발생할 수 있는 하이드레이션 경고를 방지해 줍니다.
사용법
useTheme 훅을 사용하여 현재 테마를 확인하고 변경할 수 있습니다.
'use client';
import { Button } from '@vapor-ui/components/ui/button';
import { useTheme } from '@vapor-ui/core';
export function ThemeToggle() {
const { resolvedTheme, setTheme, mounted } = useTheme();
// SSR 환경에서 hydration 완료 전까지 로딩 상태 표시
if (!mounted) {
return null;
}
return (
<Button
variant="ghost"
onClick={() => setTheme(resolvedTheme === 'light' ? 'dark' : 'light')}
>
{resolvedTheme === 'light' ? 'Dark' : 'Light'}
</Button>
);
}useTheme Hook
useTheme 훅은 다음과 같은 유용한 값들을 반환합니다.
| Key | Type | Description |
|---|---|---|
theme | 'light' | 'dark' | 'system' | undefined | 현재 설정된 테마. SSR 환경에서 mounted가 false일 때는 undefined. |
setTheme | (theme: 'light' | 'dark' | 'system' | ((prev: Theme) => Theme)) => void | 테마를 변경하는 함수. 함수형 업데이트도 지원하며 localStorage에 자동 저장됩니다. |
themes | ('light' | 'dark' | 'system')[] | 사용 가능한 테마 목록. 항상 ['light', 'dark', 'system']을 포함합니다. |
resolvedTheme | 'light' | 'dark' | undefined | 실제로 적용된 테마. theme이 'system'일 때 현재 시스템 테마를 반영합니다. |
systemTheme | 'light' | 'dark' | undefined | 현재 사용자의 시스템 테마. theme이 'system'일 때만 제공됩니다. |
forcedTheme | 'light' | 'dark' | 'system' | undefined | 강제로 적용된 테마. 설정되지 않았을 때는 undefined. |
resetTheme | () => void | 테마 설정을 기본값으로 초기화하고 localStorage에서 저장된 값을 제거합니다. |
mounted | boolean | ThemeProvider가 클라이언트에서 마운트되었는지 여부. SSR 환경에서 hydration 이슈 방지를 위해 사용. |
ThemeProvider Props
ThemeProvider에 전달할 수 있는 props는 다음과 같습니다.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultTheme | 'light' | 'dark' | 'system' | 'system' | 테마 동작을 결정합니다. 'light'/'dark'는 고정 테마, 'system'은 시스템 테마에 자동 동기화됩니다. |
storageKey | string | 'vapor-ui-theme' | localStorage에 테마를 저장할 때 사용될 키. |
forcedTheme | 'light' | 'dark' | 'system' | undefined | 특정 테마를 강제로 적용합니다. 이 값이 설정되면 다른 모든 테마 관련 설정을 무시합니다. |
disableTransitionOnChange | boolean | false | true일 경우, 테마 변경 시 발생하는 CSS 트랜지션을 비활성화합니다. |
enableColorScheme | boolean | true | true일 경우, color-scheme CSS 속성을 자동으로 설정하여 브라우저 UI(스크롤바 등)의 테마를 조정합니다. |
nonce | string | undefined | undefined | CSP(Content Security Policy) nonce 값. 보안 정책이 적용된 환경에서 사용. |
로컬 테마 스코프 (ThemeScope)
ThemeScope 컴포넌트를 사용하여 특정 영역에만 다른 테마를 적용할 수 있습니다.
import { Card } from '@vapor-ui/components/ui/card';
import { ThemeScope } from '@vapor-ui/core';
export function ThemeScopeExample() {
return (
<div>
<Card>전역 테마가 적용된 카드</Card>
<ThemeScope forcedTheme="dark">
<Card>다크 테마가 강제 적용된 카드</Card>
</ThemeScope>
<ThemeScope forcedTheme="light">
<Card>라이트 테마가 강제 적용된 카드</Card>
</ThemeScope>
</div>
);
}Portal과 함께 사용하기
Portal 컴포넌트(Dialog, Popover, Tooltip 등)가 ThemeScope의 테마를 상속받으려면, Portal의 컨테이너를 ThemeScope 영역 내부 요소로 지정하면 됩니다.
<ThemeScope forcedTheme="dark">
<section ref={sectionRef}>
{' '}
{/* 어떤 요소든 가능 */}
<Dialog.Content portalProps={{ container: sectionRef.current }}>
{/* 이 Portal은 다크 테마를 상속받음 */}
</Dialog.Content>
</section>
</ThemeScope>ThemeScope Props
| Prop | Type | Description |
|---|---|---|
forcedTheme | 'light' | 'dark' | 해당 영역에 강제로 적용할 테마. |
children | React.ReactNode | 테마가 적용될 자식 컴포넌트들. |
style | CSSProperties | undefined | 추가 스타일. colorScheme는 자동으로 설정됨. |
고급 사용법
SSR 및 Hydration 처리
서버 사이드 렌더링(SSR) 환경에서는 서버와 클라이언트 간 테마 정보 차이로 인한 hydration 오류가 발생할 수 있습니다. Vapor UI는 이를 자동으로 처리합니다:
mounted상태: 클라이언트 마운트 완료 여부를 추적- 조건부 렌더링:
mounted가false일 때는 안전한 기본값 사용 - 자동 동기화: 마운트 완료 후 실제 테마 값으로 업데이트
테마 동작 방식
defaultTheme 설정에 따라 테마 동작이 결정됩니다:
고정 테마 ('light' 또는 'dark')
<ThemeProvider defaultTheme="light">- 항상 지정된 테마로 고정됩니다
- 시스템 테마 변경을 무시합니다
- 사용자가
setTheme('system')으로 변경할 수는 있습니다
시스템 동기화 ('system')
<ThemeProvider defaultTheme="system">- 운영체제 테마 설정을 자동으로 감지합니다
- 시스템 테마 변경 시 자동으로 업데이트됩니다
내부적으로 prefers-color-scheme CSS 미디어 쿼리를 사용하여 실시간으로 변경사항을 감지합니다.
테마 적용 우선순위
테마는 다음 순서대로 적용됩니다:
forcedTheme:ThemeProvider에 설정된 강제 테마 (가장 높은 우선순위)localStorage:setTheme()호출로 사용자가 저장한 테마 (사용자 선택 존중)defaultTheme: 초기 테마 설정 (localStorage에 저장된 값이 없을 때 사용)
'light'/'dark': 해당 테마로 고정'system': 시스템 테마에 동기화
다중 탭 동기화
Vapor UI는 여러 브라우저 탭 간 테마 설정을 자동으로 동기화합니다. 한 탭에서 테마를 변경하면 같은 도메인의 다른 탭들도 자동으로 업데이트됩니다.
TypeScript 지원
import type { ThemeConfig, ThemeScopeProps, UseThemeProps } from '@vapor-ui/core';
// 시스템 테마에 자동 동기화
const systemConfig: ThemeConfig = {
defaultTheme: 'system',
};
// 라이트 테마로 고정
const lightConfig: ThemeConfig = {
defaultTheme: 'light',
};
const MyComponent = () => {
const themeData: UseThemeProps = useTheme();
// ...
};