Getting Started

Theming

라이트, 다크, 시스템 테마를 자동으로 지원하는 Vapor UI 테마 시스템을 설정하세요.

이 문서에서는 Vapor UI의 테마 시스템 설정 방법을 설명합니다. ThemeProvider를 사용하여 라이트 모드, 다크 모드, 시스템 동기화를 구현할 수 있습니다.

설정

패키지 설치

npm install @vapor-ui/core@beta

ThemeProvider 설정

애플리케이션 최상위를 ThemeProvider로 감싸서 테마 시스템을 활성화합니다.

app/layout.tsx
import { ThemeProvider } from '@vapor-ui/core';

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="ko" suppressHydrationWarning>
            <body>
                <ThemeProvider defaultTheme="system">{children}</ThemeProvider>
            </body>
        </html>
    );
}

SSR 환경에서는 suppressHydrationWarning<html> 태그에 추가하여 하이드레이션 경고를 방지합니다.

테마 제어

useTheme 훅으로 테마를 읽고 변경합니다. 아래는 테마 토글 버튼 구현 예제입니다.

components/theme-toggle.tsx
'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 훅 레퍼런스

useTheme 훅이 반환하는 값과 함수입니다.

KeyTypeDescription
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에서 저장된 값을 제거합니다.
mountedbooleanThemeProvider가 클라이언트에서 마운트되었는지 여부. SSR 환경에서 hydration 이슈 방지를 위해 사용.

ThemeProvider 설정 옵션

PropTypeDefaultDescription
defaultTheme'light' | 'dark' | 'system''system'테마 동작을 결정합니다. 'light', 'dark'는 고정 테마, 'system'은 시스템 테마에 자동 동기화됩니다.
storageKeystring'vapor-ui-theme'localStorage에 테마를 저장할 때 사용될 키.
forcedTheme`'light' | 'dark' | 'system'undefined특정 테마를 강제로 적용합니다. 이 값이 설정되면 다른 모든 테마 관련 설정을 무시합니다.
disableTransitionOnChangebooleanfalsetrue일 경우, 테마 변경 시 발생하는 CSS 트랜지션을 비활성화합니다.
enableColorSchemebooleantruetrue일 경우, color-scheme CSS 속성을 자동으로 설정하여 브라우저 UI(스크롤바 등)의 테마를 조정합니다.
noncestring | undefinedundefinedCSP(Content Security Policy) nonce 값. 보안 정책이 적용된 환경에서 사용.

부분 테마 적용

특정 영역에만 다른 테마를 적용하려면 ThemeScope를 사용합니다.

components/theme-scope-example.tsx
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 컴포넌트와 함께 사용

Dialog, Popover 같은 Portal 컴포넌트가 ThemeScope의 테마를 상속받으려면 컨테이너를 지정합니다.

<ThemeScope forcedTheme="dark">
    <section ref={sectionRef}>
        {' '}
        {/* 어떤 요소든 가능 */}
        <Dialog.Content portalProps={{ container: sectionRef.current }}>
            {/* 이 Portal은 다크 테마를 상속받음 */}
        </Dialog.Content>
    </section>
</ThemeScope>

ThemeScope 옵션

PropTypeDescription
forcedTheme'light' | 'dark'해당 영역에 강제로 적용할 테마
childrenReact.ReactNode테마가 적용될 자식 컴포넌트들
styleCSSProperties | undefined추가 스타일 (colorScheme 자동 설정)

고급 기능

SSR 처리

서버-클라이언트 테마 차이로 인한 hydration 오류를 자동으로 방지합니다.

  • mounted 상태 추적: 클라이언트 마운트 완료 여부를 확인합니다.
  • 안전한 기본값: 마운트 전까지 충돌 없는 값을 사용합니다.
  • 자동 동기화: 마운트 완료 후 실제 테마로 업데이트합니다.

테마 동작 모드

고정 테마 ('light' 또는 'dark')

  • 지정된 테마로 고정됩니다.
  • 시스템 테마 변경을 무시합니다.
  • 사용자가 수동으로 변경할 수 있습니다.

시스템 동기화 ('system')

  • 운영체제 테마 설정을 자동으로 감지합니다.
  • prefers-color-scheme 미디어 쿼리로 실시간 추적합니다.
  • 시스템 변경 시 자동으로 업데이트됩니다.

우선순위

테마는 다음 순서로 적용됩니다.

  1. forcedTheme: 강제 테마 (최우선)
  2. localStorage: 사용자가 선택한 테마
  3. defaultTheme: 기본 설정값

다중 탭 동기화

여러 브라우저 탭 간 테마 설정을 자동으로 동기화합니다. 한 탭에서 테마를 변경하면 같은 도메인의 다른 탭도 즉시 업데이트됩니다.

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();
    // ...
};