API Reference

Theme API

The three React exports of @refraction-ui/react/theme ThemeProvider, ThemeScript, and useTheme.

Installation

$pnpm add @refraction-ui/react

Full setup (SSR-safe)

Always pair <ThemeScript> in <head> with <ThemeProvider> in <body>, and keep their props in lockstep.

tsx
// app/layout.tsx — Next.js App Router
import { ThemeProvider, ThemeScript } from '@refraction-ui/react/theme'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        {/* Mirror these props in ThemeProvider below */}
        <ThemeScript defaultMode="light" storageKey="app_theme" />
      </head>
      <body>
        <ThemeProvider defaultMode="light" storageKey="app_theme">
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

ThemeProvider props

PropTypeDefaultDescription
defaultMode'light' | 'dark' | 'system''system'Theme applied when nothing is stored. Mirror this in <ThemeScript> for SSR.
storageKeystring'rfr-theme'localStorage key used to persist the user choice.
attribute'class' | 'data-theme''class'How the theme is applied to <html>. 'class' toggles `light` / `dark` classes.
enableSystembooleantrueWhen false, the provider never falls through to `prefers-color-scheme` — `defaultMode` wins on first visit.

ThemeScript props

Inline <script> placed in <head> to prevent the dark-flash on first paint. Issue #317: defaultMode + enableSystem are now honoured, so the script no longer silently falls through to prefers-color-scheme when the provider says otherwise.

PropTypeDefaultDescription
defaultMode'light' | 'dark' | 'system''system'Theme to apply when nothing is stored. Match your <ThemeProvider defaultMode> so the pre-paint script and provider agree on the no-storage path (issue #317).
enableSystembooleantrueWhen false, the inline script never reads `prefers-color-scheme`. Combined with `defaultMode="light"`, this is the fix for the dark-flash → brand-light flicker on first visit.
storageKeystring'rfr-theme'Must match the ThemeProvider value.
attribute'class' | 'data-theme''class'Must match the ThemeProvider value.

useTheme

tsx
'use client'
import { useTheme } from '@refraction-ui/react/theme'

export function ThemeToggle() {
  const { resolvedTheme, toggle } = useTheme()
  return (
    <button onClick={toggle} aria-label="Toggle theme">
      {resolvedTheme === 'dark' ? '☀︎' : '☾'}
    </button>
  )
}
PropTypeDefaultDescription
mode'light' | 'dark' | 'system'--The current user selection.
resolvedTheme'light' | 'dark'--What is actually applied (system resolved).
setMode(mode) => void--Persist a new selection.
toggle() => void--Convenience flip light ↔ dark.