다크 모드

Tailwind v4 패키지는 시맨틱 토큰 시스템을 통해 다크 모드를 지원합니다. 모든 색상이 CSS 변수를 참조하므로, 다크 모드로 전환하는 것은 해당 변수를 재정의하기만 하면 됩니다.

작동 방식

컴포넌트는 bg-primary, text-foreground, border-border와 같은 시맨틱 유틸리티 클래스를 사용합니다. 이 클래스들은 프로젝트의 CSS에서 정의하는 CSS 변수(--color-primary, --color-foreground 등)로 해석됩니다. 다크 모드는 다크 셀렉터 아래에 이러한 변수들에 대한 대체 값을 제공함으로써 작동합니다.

설정

data-theme 속성 사용하기

data-theme 셀렉터를 사용하여 라이트 및 다크 토큰 값을 정의합니다:

@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
--color-popover: oklch(1 0 0);
--color-popover-foreground: oklch(0.145 0.004 285.823);
--color-primary: oklch(0.205 0.006 285.885);
--color-primary-foreground: oklch(0.985 0.001 285.823);
--color-muted-foreground: oklch(0.556 0.01 285.823);
--color-accent: oklch(0.96 0.003 285.823);
--color-accent-foreground: oklch(0.205 0.006 285.885);
--color-destructive: oklch(0.577 0.245 27.325);
--color-border: oklch(0.922 0.004 285.823);
--color-input: oklch(0.922 0.004 285.823);
--color-ring: oklch(0.87 0.006 285.823);
}
[data-theme="dark"] {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
--color-popover: oklch(0.145 0.004 285.823);
--color-popover-foreground: oklch(0.985 0.001 285.823);
--color-primary: oklch(0.985 0.001 285.823);
--color-primary-foreground: oklch(0.205 0.006 285.885);
--color-muted-foreground: oklch(0.556 0.01 285.823);
--color-accent: oklch(0.269 0.006 285.885);
--color-accent-foreground: oklch(0.985 0.001 285.823);
--color-destructive: oklch(0.577 0.245 27.325);
--color-border: oklch(0.269 0.006 285.885);
--color-input: oklch(0.269 0.006 285.885);
--color-ring: oklch(0.369 0.006 285.885);
}

속성을 설정하여 다크 모드를 토글합니다:

function App() {
const [dark, setDark] = useState(false);
return (
<div data-theme={dark ? "dark" : "light"}>
<button onClick={() => setDark(!dark)}>Toggle Theme</button>
<DatePicker value={value} onChange={setValue} />
</div>
);
}

.dark 클래스 사용하기

프레임워크가 .dark 클래스를 사용하는 경우 (예: next-themes를 사용하는 Next.js), 해당 클래스 아래에 토큰을 정의합니다:

@theme {
/* Light tokens */
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
/* ... */
}
.dark {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
/* ... */
}

시스템 설정 사용하기

OS 설정을 따르려면 @media (prefers-color-scheme: dark)를 사용합니다:

@theme {
/* Light tokens */
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
/* ... */
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
/* ... */
}
}

next-themes와 통합

Next.js와 next-themes를 함께 사용하는 일반적인 패턴입니다:

app/layout.tsx
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
</body>
</html>
);
}
globals.css
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
--color-primary: oklch(0.205 0.006 285.885);
--color-primary-foreground: oklch(0.985 0.001 285.823);
/* ... all light tokens */
}
.dark {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
--color-primary: oklch(0.985 0.001 285.823);
--color-primary-foreground: oklch(0.205 0.006 285.885);
/* ... all dark tokens */
}

테마가 변경되면 피커가 자동으로 올바른 토큰 값을 선택합니다.

다크 모드에서의 하이라이트 점

하이라이트 점은 after:bg-amber-500 dark:after:bg-amber-400(토큰이 아닌 구체적인 Tailwind 색상)을 사용하여 두 모드 모두에서 좋은 가시성을 보장합니다. 이것이 컴포넌트에서 유일하게 시맨틱하지 않은 색상입니다. 변경해야 하는 경우, 커스텀 Day 렌더와 함께 Compound Component API를 사용하세요:

<DatePicker.Day date={date}>
{(props) => (
<button /* ... */>
{props.day}
{props.isHighlighted && (
<span className="absolute bottom-0.5 w-1 h-1 rounded-full bg-emerald-500 dark:bg-emerald-400" />
)}
</button>
)}
</DatePicker.Day>

특정 모드 강제하기

시스템 설정과 관계없이 라이트 또는 다크 모드를 강제하려면 테마를 명시적으로 설정하세요:

{
/* 항상 라이트 */
}
<div data-theme="light">
<DatePicker value={value} onChange={setValue} />
</div>;
{
/* 항상 다크 */
}
<div data-theme="dark">
<DatePicker value={value} onChange={setValue} />
</div>;