Skip to main content
Apsara includes built-in dark mode support with automatic theme switching, system preference detection, and smooth transitions between themes.

Quick start

Implement dark mode in three simple steps:
1

Import the stylesheet

Import Apsara’s CSS file which includes both light and dark theme variables:
2

Wrap your app with ThemeProvider

Add the ThemeProvider component at the root of your application:
3

Add a theme switcher

Use the ThemeSwitcher component to let users toggle themes:

ThemeProvider

The ThemeProvider manages theme state, persists user preferences, and handles system theme detection.

Basic usage

Props

string[]
default:"[\"light\", \"dark\"]"
List of available theme names.
string
default:"\"light\""
Default theme name. Use "system" to respect user’s system preference.
boolean
default:"true"
Enable system theme detection using prefers-color-scheme.
string
default:"\"theme\""
LocalStorage key for persisting theme preference.
string
default:"\"data-theme\""
HTML attribute to set on documentElement. Use "class" for class-based theming.
boolean
default:"true"
Update the color-scheme CSS property for native form elements.
boolean
default:"false"
Disable CSS transitions when switching themes to prevent flash.
string
Force a specific theme (useful for component previews or documentation).
'modern' | 'traditional'
default:"\"modern\""
Style variant affecting border radius and typography.
'indigo' | 'orange' | 'mint'
default:"\"indigo\""
Accent color for interactive elements.
'gray' | 'mauve' | 'slate'
default:"\"gray\""
Gray color variant for neutral elements.

useTheme hook

Access and control the theme programmatically:

Return values

string
Current active theme name (e.g., "light", "dark", or "system").
(theme: string) => void
Function to update the theme.
string
The actual rendered theme. If theme is "system", this returns "light" or "dark" based on system preference.
'light' | 'dark' | undefined
System’s theme preference (only available when enableSystem is true).
string[]
List of available theme names.
string | undefined
Forced theme if one is set.

ThemeSwitcher component

A pre-built toggle button for switching between light and dark themes:
The component automatically:
  • Shows a sun icon in dark mode
  • Shows a moon icon in light mode
  • Toggles between light and dark on click
  • Uses Radix UI icons for crisp rendering

Props

number
default:"30"
Size of the icon in pixels.

Custom theme switcher

Build your own theme switcher using the useTheme hook:

Multi-theme support

Support more than two themes:
Define additional theme CSS:

Server-side rendering

The ThemeProvider includes a script that prevents theme flashing on page load:
The provider automatically injects a blocking script in the <head> that:
  1. Reads the theme from localStorage
  2. Detects system preference if theme is “system”
  3. Applies the theme before React hydrates
  4. Prevents flash of wrong theme
The disableTransitionOnChange prop prevents CSS transitions during theme changes, which is especially useful on initial page load.

Styling for dark mode

Apsara’s CSS automatically adapts based on the data-theme attribute:
In your custom styles:

Best practices

Most users prefer apps that respect their system theme. Set defaultTheme="system" and enableSystem={true} for the best user experience.
The default storageKey saves the theme to localStorage. Users won’t have to reselect their preference on each visit.
Ensure sufficient contrast in both themes. Aim for WCAG AA compliance (4.5:1 for normal text, 3:1 for large text).
Use disableTransitionOnChange to prevent the flash of animations when the page first loads.
Make theme changes obvious with clear visual differences, not just subtle color shifts.

Examples

Next.js app

app/layout.tsx

Vite/React app

main.tsx

Theming

Learn about CSS variables and design tokens

Styling

Understand the vanilla CSS approach