reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Overview
Start
Next.js 설치
Private install
Core Concepts
아키텍처
Composition Patterns
Accessibility
Keyboard Patterns
Styling
Theme System
Advanced Patterns
Build & Operate
Skills
AI Integration
CLI (opt surface add)
Dependency Graph
Tools
Canvas Catalog
Theme Builder
Form Builder
Templates
Templates
Releases
Release Notes
Oopt-ui
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

Core Concepts
  1. Docs
  2. /
  3. Core Concepts
  4. /
  5. Theme System

테마 시스템

Compound Theme 아키텍처 — 5개 프리셋 × light/dark 모드를 하나의 data-theme 속성으로 통합 관리합니다.

reopt design · Updated Jun 26, 2026

개요

opt-ui의 테마 시스템은 프리셋(preset)과 모드(mode)를 합쳐서 하나의 compound theme name으로 관리합니다. 기본 통합에서는 OptThemeProvider가 상태를 관리하고, data-theme HTML 속성 하나로 프리셋과 라이트/다크 모드를 동시에 제어합니다.

프리셋LightDark
Defaultdefaultdefault-dark
Minimalminimalminimal-dark
Naturalnatural없음 (force-light)
Propropro-dark
Mono Darkmono-darkmono-dark-dark

CSS 변수 체계

모든 시각적 속성은 --opt-* 네임스페이스의 CSS 커스텀 프로퍼티로 정의됩니다. Tailwind v4의 @theme inline으로 유틸리티 클래스에 매핑되어 있어 bg-surface, text-text-primary, border-border 등을 직접 사용합니다.

카테고리CSS 변수Tailwind 유틸리티
배경--opt-bg, --opt-bg-subtle, --opt-bg-mutedbg-bg, bg-bg-subtle, bg-bg-muted
Surface--opt-surface, --opt-surface-raised, --opt-surface-overlaybg-surface, bg-surface-raised
Accent--opt-accent, --opt-accent-hover, --opt-accent-subtlebg-accent, text-accent, ring-accent
텍스트--opt-text, --opt-text-secondary, --opt-text-tertiarytext-text-primary, text-text-secondary
테두리--opt-border, --opt-border-hover, --opt-border-subtleborder-border, border-border-hover
상태--opt-success, --opt-warning, --opt-danger, --opt-infotext-success, bg-danger-subtle
차트--opt-chart-1 ~ --opt-chart-5fill-chart-1, stroke-chart-2
타이포그래피--opt-font-heading, --opt-font-body, --opt-tracking-heading, --opt-weight-headingfont-heading, font-sans (body)
스페이싱--opt-space-section, --opt-space-group, --opt-space-elementgap-section, gap-group, gap-element

Radius, Shadow, Spacing도 CSS 변수로 제어되어 프리셋 전환만으로 전체 룩앤필이 바뀝니다:

css
/* 프리셋별 오버라이드 예시 — Pro (Sharp/Dense) vs Mono Dark (OLED) */
[data-theme="pro"]        { --opt-radius-md: 0.25rem; --opt-shadow-md: 0 2px 6px rgba(0,80,200,.08); }
[data-theme="mono-dark"]  { --opt-radius-md: 0.25rem; --opt-space-section: 0.75rem; }
[data-theme="minimal"]    { --opt-radius-md: 0.125rem; --opt-shadow-md: none; }

프리셋별 차별화

각 프리셋은 색상뿐 아니라 배경 틴트, 스페이싱 밀도, 타이포그래피가 모두 달라 전환 시 완전히 다른 인터페이스처럼 보입니다.

프리셋배경 틴트밀도 (section / group / element)헤딩 폰트본문 폰트
DefaultWhite1.5 / 1 / 0.5remGeist SansGeist Sans
MinimalWhite1.5 / 1 / 0.5remGeist SansGeist Sans
NaturalWarm parchment1.75 / 1.25 / 0.625rem (넉넉)Playfair DisplayLora
ProCool gray0.875 / 0.5 / 0.25rem (초촘촘)Geist MonoGeist Sans
Mono DarkOLED black0.75 / 0.5 / 0.25rem (고밀도)Geist MonoGeist Sans

타이포그래피는 CSS 변수 기반이므로 h1~h6 태그에 자동 적용됩니다. globals.css 에서 다음과 같이 설정되어 있습니다:

css
body { font-family: var(--opt-font-body); }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--opt-font-heading);
  letter-spacing: var(--opt-tracking-heading);
  font-weight: var(--opt-weight-heading);
}

dark: 변형의 동작 원리

Tailwind v4의 @custom-variant로 dark: 접두사를 오버라이드합니다. 기존 .dark 클래스 대신 data-theme이 -dark로 끝나는지 확인합니다.

css
/* globals.css */
@import "tailwindcss";
@custom-variant dark (&:where([data-theme$="-dark"], [data-theme$="-dark"] *));

이로써 앱 전체의 dark: 유틸리티 클래스가 수정 없이 compound theme과 연동됩니다. CSS 프리셋 파일에서는 light/dark 셀렉터만 분리하면 됩니다:

css
/* presets/mono-dark.css */
[data-theme="mono-dark"]      { --opt-bg: hsl(220 15% 98%); --opt-text: hsl(220 15% 12%); }
[data-theme="mono-dark-dark"] { --opt-bg: #000; --opt-text: hsl(220 12% 94%); }

Provider 설정

기본 통합은 앱 루트에서 OptThemeProvider로 앱을 감싸는 방식입니다. provider가 프리셋/모드 상태를 관리하고, 계산된 compound theme 이름을 data-theme 속성에 반영합니다.

tsx
import { OptThemeProvider } from "@reopt-ai/opt-ui";

export function Providers({ children }: { children: React.ReactNode }) {
  return <OptThemeProvider defaultPreset="default">{children}</OptThemeProvider>;
}

useOptTheme API

useOptTheme() 훅은 프리셋과 모드를 읽고 변경하는 유일한 인터페이스입니다.

tsx
const {
  preset,       // "default" | "minimal" | "natural" | "pro" | "mono-dark"
  setPreset,    // (p: ThemePreset) => void
  mode,         // "light" | "dark" | "system"
  setMode,      // (m: ThemeMode) => void
  resolvedMode, // "light" | "dark" — system 모드 해석 결과
} = useOptTheme();
속성타입설명
presetThemePreset현재 선택된 프리셋
setPreset(p: ThemePreset) => void프리셋 변경 (localStorage 자동 저장)
modeThemeMode사용자가 선택한 모드 (light / dark / system)
setMode(m: ThemeMode) => void모드 변경 (localStorage 자동 저장)
resolvedMode"light" | "dark"system 모드일 때 OS 설정을 반영한 최종 모드

사용 예시

ThemeSwitcher Shell은 프리셋 그리드와 모드 토글을 함께 제공하는 완성된 UI입니다. 직접 구현하려면 useOptTheme()를 사용합니다.

tsx
// 1. ThemeSwitcher Shell 사용 (가장 간단)
import { ThemeSwitcher } from "@reopt-ai/opt-ui";

<ThemeSwitcher />               // 프리셋 그리드 + 모드 토글
<ThemeSwitcher variant="compact" />  // 프리셋 스와치 + 모드 아이콘
tsx
// 2. 커스텀 모드 토글 구현
import { Moon, Sun } from "lucide-react";
import { useOptTheme } from "@reopt-ai/opt-ui";

function ModeToggle() {
  const { mode, setMode, resolvedMode } = useOptTheme();

  return (
    <button onClick={() => setMode(resolvedMode === "light" ? "dark" : "light")}>
      {resolvedMode === "light" ? <Moon className="size-4" /> : <Sun className="size-4" />}
    </button>
  );
}
tsx
// 3. Command Palette에서 모드 전환
import { useOptTheme } from "@reopt-ai/opt-ui";

const { setMode } = useOptTheme();

switch (cmd.id) {
  case "theme-light":  setMode("light");  break;
  case "theme-dark":   setMode("dark");   break;
}

Force-light 프리셋

FORCE_LIGHT_PRESETS에 포함된 프리셋(현재 Natural)은 모드 설정과 무관하게 항상 라이트 모드로 렌더링됩니다. CSS 프리셋 파일에 dark 섹션이 없으며, compound theme 이름에 -dark 접미사가 붙지 않습니다.

tsx
import { FORCE_LIGHT_PRESETS } from "@reopt-ai/opt-ui";

// Natural 프리셋 여부 확인
if (FORCE_LIGHT_PRESETS.has(preset)) {
  // 모드 토글 비활성화, 항상 light
}

FOUC 방지

페이지 로드 시 테마 깜빡임(FOUC)을 방지하기 위해 <head>에 인라인 스크립트를 삽입합니다. 이 스크립트는 React 하이드레이션 전에 실행되어 data-theme 속성을 즉시 설정합니다.

tsx
// app/layout.tsx — <head> 안에 삽입
import { createThemeBootScript } from "@reopt-ai/opt-ui";

<script
  dangerouslySetInnerHTML={{
    __html: createThemeBootScript(),
  }}
/>

이 helper는 opt-preset, opt-mode, 레거시 opt-theme까지 읽어서 provider와 같은 규칙으로 compound theme 이름을 계산합니다.

새 프리셋 추가하기

새 테마 프리셋을 추가하려면 아래 5단계를 따릅니다:

  1. CSS 파일 생성: packages/opt-ui/src/lib/theme/presets/my-theme.css에 [data-theme="my-theme"]와 [data-theme="my-theme-dark"] 셀렉터 정의
  2. globals.css에 import: @import ".../presets/my-theme.css";
  3. 타입 추가: ThemePreset 유니온에 "my-theme" 추가, CompoundTheme에 "my-theme" | "my-theme-dark" 추가
  4. next-themes interop를 쓴다면: ALL_COMPOUND_THEMES 배열에 두 compound 이름 추가
  5. Theme registry 추가: label, description, swatches 정의

force-light 프리셋으로 만들려면 CSS에 dark 셀렉터를 생략하고 FORCE_LIGHT_PRESETS Set에 추가합니다. 초기 렌더용 스크립트는 createThemeBootScript()가 같은 규칙을 자동으로 사용합니다.

저장소 & 마이그레이션

테마 설정은 localStorage에 두 개의 키로 저장됩니다:

키값기본값
opt-presetThemePreset"default"
opt-modeThemeMode"system"

기존 opt-theme 키는 첫 로드 시 자동으로 opt-preset으로 마이그레이션 됩니다. 쿠키는 더 이상 사용하지 않습니다.

Theme Builder

인터랙티브 Theme Builder를 사용하면 코드를 작성하지 않고도 53개 CSS 토큰을 실시간으로 편집할 수 있습니다. 5개 프리셋 중 하나를 기반으로 시작하여, 수정된 토큰만 포함된 커스텀 프리셋 CSS를 다운로드합니다.

기능설명
라이브 미리보기8개 탭(Essential, Forms, Data, Feedback, Nav, Type, Layers, Palette)으로 토큰 변경 효과를 즉시 확인
토큰 검색 & 필터이름/변수명 검색, “변경된 토큰만” 필터로 빠르게 탐색
색상 그룹 스트립Accent, Status, Chart 색상을 그룹별 스워치로 한 눈에 편집. 인라인 HSL 슬라이더 지원
WCAG 대비 표시텍스트 색상 편집 시 Surface 배경 대비 AAA/AA/Fail 자동 계산
Accent 자동 파생기본 Accent 색상 변경 시 hover/active/subtle/fg 5개 토큰 자동 계산
Undo / RedoCtrl+Z / Ctrl+Shift+Z (최대 50단계)
CSS 내보내기[data-theme="..."] 형식 CSS 다운로드 또는 클립보드 복사
CSS 가져오기기존 커스텀 프리셋 CSS 파일을 불러와서 계속 편집

키보드 단축키: C 코드 패널 토글, L/D 모드 전환, 1-6 프리셋 빠른 전환, Ctrl+S CSS 다운로드

Theme Builder 열기

내보내기(export) 참조

tsx
// @reopt-ai/opt-ui에서 사용 가능한 테마 관련 export

// 컴포넌트 & 훅
export { OptThemeProvider, useOptTheme } from "@reopt-ai/opt-ui";
export { ThemeSwitcher } from "@reopt-ai/opt-ui";

// 상수 & 유틸리티
export { FORCE_LIGHT_PRESETS }  from "@reopt-ai/opt-ui";  // ReadonlySet<ThemePreset>
export { ALL_COMPOUND_THEMES }  from "@reopt-ai/opt-ui";  // next-themes interop용 CompoundTheme[]
export { getCompoundTheme }     from "@reopt-ai/opt-ui";  // (preset, mode) => CompoundTheme
export { createThemeBootScript } from "@reopt-ai/opt-ui"; // 초기 렌더용 data-theme script

// 타입
export type { ThemePreset }   from "@reopt-ai/opt-ui";  // "default" | "minimal" | ... | "mono-dark"
export type { ThemeMode }     from "@reopt-ai/opt-ui";  // "light" | "dark" | "system"
export type { CompoundTheme } from "@reopt-ai/opt-ui";  // "default" | "default-dark" | ...
PreviousStylingdata-attribute 셀렉터, className 오버라이드, 다크모드, 애니메이션Core Concepts
Go to Styling
NextAdvanced PatternsDataTable 필터링, DashboardGrid 반응형, Toast 알림, Combobox 그룹화Core Concepts