reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
개요
시작하기
Next.js 설치
Private install
핵심 개념
아키텍처
컴포지션 패턴
접근성
키보드 패턴
스타일링
테마 시스템
고급 패턴
구축·운영
Skills
AI 연동
CLI (opt surface add)
의존 그래프
도구
Canvas 카탈로그
Theme Builder
Form Builder
템플릿
템플릿
릴리즈
릴리즈 노트
Oopt-ui
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

핵심 개념
  1. 문서
  2. /
  3. 핵심 개념
  4. /
  5. 테마 시스템

테마 시스템

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

reopt design · 업데이트 2026년 6월 26일

개요

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" | ...
Previous스타일링data-attribute 셀렉터, className 오버라이드, 다크모드, 애니메이션핵심 개념
스타일링 페이지로 이동
Next고급 패턴DataTable 필터링, DashboardGrid 반응형, Toast 알림, Combobox 그룹화핵심 개념