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
TheThemeProvider 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:- 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 theuseTheme hook:
Multi-theme support
Support more than two themes:Server-side rendering
The ThemeProvider includes a script that prevents theme flashing on page load:<head> that:
- Reads the theme from localStorage
- Detects system preference if theme is “system”
- Applies the theme before React hydrates
- 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 thedata-theme attribute:
Best practices
Enable system preference by default
Enable system preference by default
Most users prefer apps that respect their system theme. Set
defaultTheme="system" and enableSystem={true} for the best user experience.Persist user choice
Persist user choice
The default
storageKey saves the theme to localStorage. Users won’t have to reselect their preference on each visit.Test color contrast
Test color contrast
Ensure sufficient contrast in both themes. Aim for WCAG AA compliance (4.5:1 for normal text, 3:1 for large text).
Disable transitions on mount
Disable transitions on mount
Use
disableTransitionOnChange to prevent the flash of animations when the page first loads.Provide visual feedback
Provide visual feedback
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
Related resources
Theming
Learn about CSS variables and design tokens
Styling
Understand the vanilla CSS approach