output.mp4
React theme switching with smooth view transition animations, multi-theme support, and synchronized state management.
Version 2 introduces new features for enhanced color theme management and improved Vite integration.
- New functions to directly toggle between different color themes, without using the ThemeSelector component
- Vite theme provider now supports theme toggling and persistence
- Functions to manage and check active themes
- Functions to toggle light and dark themes directly
import { useThemeAnimation } from '@space-man/react-theme-animation'
function ColorThemeDemo() {
const { createColorThemeToggle, isColorThemeActive, toggleDarkTheme, toggleLightTheme } =
useThemeAnimation({
colorThemes: ['supabase'],
})
return (
<>
<button onClick={createColorThemeToggle('supabase')} className="theme-toggle-btn">
Supabase Theme {isColorThemeActive('supabase') ? '✅' : '❌'}
</button>
<button ref={ref} onClick={toggleDarkTheme} className="theme-toggle-btn">
Dark Theme
</button>
<button ref={ref} onClick={toggleLightTheme} className="theme-toggle-btn">
Light Theme
</button>
</>
)
}
// Vite Theme Provider Example
function App() {
return (
<ViteThemeProvider
themes={['light', 'dark', 'system']}
colorThemes={['default', 'supabase']}
defaultTheme="system"
defaultColorTheme="default"
>
<ThemeToggle />
</ViteThemeProvider>
)
}
function ThemeToggle() {
const { createColorThemeToggle, isColorThemeActive } = useViteTheme()
return (
<button onClick={createColorThemeToggle('supabase')} className="theme-toggle-btn">
Supabase Theme {isColorThemeActive('supabase') ? '✅' : '❌'}
</button>
)
}- Smooth Animations: Beautiful view transition animations for theme switching with customizable origins
- Multi-Theme Support: Support for light, dark, and system themes
- Color Themes: Additional color theme variants (brand colors, etc.)
- Powerful Hook:
useThemeAnimationhook with full control - Ready Components:
ThemeSwitcherandThemeSelectorcomponents - Provider Pattern: Centralized theme state management with
SpacemanThemeProvider - State Synchronization: Prevents state drift between multiple components
- Responsive: Works on all screen sizes including high-resolution displays
- Performance: Optimized animations with reduced motion support
- TypeScript: Full TypeScript support with comprehensive types
- Customizable: Extensive configuration options
- Backward Compatible: Works with existing implementations
npm install @space-man/react-theme-animationThis library supports multiple usage patterns:
- Hook-Only Usage: Direct hook usage for custom implementations
- Provider Pattern: Centralized state management (recommended)
- ViteThemeProvider: Specialized provider for Vite React SPAs
See the complete Getting Started Guide for detailed examples, advanced configurations, and best practices.
View the Next.js Code Example to see this package in action with a complete implementation of both hook and provider pattern.
View the Vite React SPA Demo for a complete Vite implementation using ViteThemeProvider.
Perfect for custom theme toggle buttons and complete control over animated theme logic.
import { useThemeAnimation } from '@space-man/react-theme-animation'
function ThemeToggle() {
const { theme, toggleTheme, ref } = useThemeAnimation()
return (
<button ref={ref} onClick={toggleTheme} className="theme-toggle-btn">
{theme === 'light' ? '🌙' : '🌞'} {theme}
</button>
)
}The most powerful pattern using SpacemanThemeProvider for centralized theme state management. This prevents state drift and provides synchronized animations.
import {
SpacemanThemeProvider,
ThemeSwitcher,
ThemeSelector,
} from '@space-man/react-theme-animation'
function App() {
return (
<SpacemanThemeProvider>
<div className="app">
<header>
<ThemeSwitcher />
</header>
<aside>
<ThemeSelector colorThemes={['default', 'supabase', 'mono']} />
</aside>
<main>
<YourAppContent />
</main>
</div>
</SpacemanThemeProvider>
)
}Specialized theme provider optimized for Vite React single-page applications. Provides essential theme management without animation features.
import { ViteThemeProvider, useViteTheme } from '@space-man/react-theme-animation'
function App() {
return (
<ViteThemeProvider defaultTheme="system" storageKey="my-app-theme" attribute="class">
<div className="app">
<header>
<ThemeToggle />
</header>
<main>
<ThemeSection />
</main>
</div>
</ViteThemeProvider>
)
}
function ThemeSection() {
return (
<SpacemanThemeProvider>
<div className="app">
<header>
<ThemeSwitcher />
</header>
<aside>
<ThemeSelector colorThemes={['default', 'supabase', 'mono']} />
</aside>
<main>
<YourAppContent />
</main>
</div>
</SpacemanThemeProvider>
)
}
function ThemeToggle() {
const { theme, setTheme, resolvedTheme } = useViteTheme()
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
{resolvedTheme === 'dark' ? '🌞' : '🌙'} {theme}
</button>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
children |
ReactNode |
- | React children |
attribute |
'class' | 'data-theme' |
'class' |
How to apply theme to DOM |
defaultTheme |
'light' | 'dark' | 'system' |
'system' |
Default theme |
enableSystem |
boolean |
true |
Enable system theme detection |
disableTransitionOnChange |
boolean |
false |
Disable CSS transitions during theme change |
storageKey |
string |
'vite-theme' |
localStorage key for persistence |
The library uses CSS custom properties for theming. Define these in your MAIN CSS file. Note: do not define the other theme variables in a separate CSS file and import it, as the :root variables for light mode will override the light mode variables for the other themes. Keep everything in the same file.
/* Base theme variables */
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
}
/* Dark theme */
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
/* Add other theme variants from design system or tweakcn */
.theme-supabase {
--background: oklch(0.9911 0 0);
--foreground: oklch(0.2046 0 0);
}
.theme-supabase.dark {
--background: oklch(0.1822 0 0);
--foreground: oklch(0.9288 0.0126 255.5078);
}
.theme-mono {
--background: oklch(1 0 0);
--foreground: oklch(0.1448 0 0);
}
.theme-mono.dark {
--background: oklch(0.1448 0 0);
--foreground: oklch(1 0 0);
}Context provider for centralized theme state management for the ThemeSelector and ThemeSwitcher components. The SpacemanThemeProvider allows you to manage themes and color themes in your application with smooth animations and synchronized state and provide a state reference for the ThemeSelector and ThemeSwitcher components.
<SpacemanThemeProvider
defaultTheme="system"
defaultColorTheme="blue"
themes={['light', 'dark', 'system']}
colorThemes={['default', 'blue', 'green', 'purple']}
animationType={ThemeAnimationType.CIRCLE}
duration={800}
onThemeChange={theme => console.log('Theme:', theme)}
onColorThemeChange={colorTheme => console.log('Color:', colorTheme)}
>
{children}
</SpacemanThemeProvider>| Property | Type | Default | Description |
|---|---|---|---|
defaultTheme |
Theme |
'system' |
Initial theme |
defaultColorTheme |
ColorTheme |
'default' |
Initial color theme |
themes |
Theme[] |
['light', 'dark', 'system'] |
Available themes |
colorThemes |
ColorTheme[] |
['default'] |
Available color themes |
animationType |
ThemeAnimationType |
ThemeAnimationType.CIRCLE |
Animation type |
duration |
number |
500 |
Animation duration in ms |
blurAmount |
number |
2 |
Blur amount for blur-circle animation |
onThemeChange |
(theme: Theme) => void |
- | Global theme change callback |
onColorThemeChange |
(colorTheme: ColorTheme) => void |
- | Global color theme change callback |
Hook to access theme state from SpacemanThemeProvider context.
const { theme, colorTheme, switchTheme, setColorTheme, switchThemeFromElement, ref } =
useSpacemanTheme()| Property | Type | Description |
|---|---|---|
theme |
Theme |
Current theme |
colorTheme |
ColorTheme |
Current color theme |
switchTheme |
(theme: Theme) => Promise<void> |
Switch to specific theme |
setColorTheme |
(colorTheme: ColorTheme) => void |
Set color theme |
switchThemeFromElement |
(theme: Theme, element: HTMLElement) => Promise<void> |
Switch theme with animation from specific element |
ref |
RefObject<HTMLElement> |
Ref for animation origin |
Standalone hook for theme management and animations.
const { theme, colorTheme, switchTheme, setColorTheme, toggleTheme, ref } = useThemeAnimation({
themes: ['light', 'dark', 'system'],
colorThemes: ['default', 'blue', 'green', 'purple'],
animationType: ThemeAnimationType.SLIDE,
duration: 800,
onThemeChange: theme => console.log('Theme changed:', theme),
onColorThemeChange: colorTheme => console.log('Color changed:', colorTheme),
})| Property | Type | Default | Description |
|---|---|---|---|
themes |
Theme[] |
['light', 'dark', 'system'] |
Available themes |
colorThemes |
ColorTheme[] |
['default'] |
Available color themes |
theme |
Theme |
'system' |
Initial theme |
colorTheme |
ColorTheme |
'default' |
Initial color theme |
animationType |
ThemeAnimationType |
ThemeAnimationType.CIRCLE |
Animation type. Options: CIRCLE, BLUR_CIRCLE, SLIDE |
duration |
number |
500 |
Animation duration in ms |
blurAmount |
number |
2 |
Blur amount for blur-circle animation |
onThemeChange |
(theme: Theme) => void |
- | Theme change callback |
onColorThemeChange |
(colorTheme: ColorTheme) => void |
- | Color theme change callback |
slideDirection |
SlideDirection |
left |
Animation slide direction |
| Property | Type | Description |
|---|---|---|
theme |
Theme |
Current theme |
colorTheme |
ColorTheme |
Current color theme |
switchTheme |
(theme: Theme) => Promise<void> |
Switch to specific theme |
setColorTheme |
(colorTheme: ColorTheme) => void |
Set color theme |
toggleTheme |
() => Promise<void> |
Toggle between light/dark |
ref |
RefObject<HTMLElement> |
Ref for animation origin |
Pre-built theme switcher component with animated buttons.
<ThemeSwitcher
themes={['light', 'dark', 'system']}
currentTheme="light"
onThemeChange={theme => console.log(theme)}
animationType={ThemeAnimationType.CIRCLE}
duration={600}
className="custom-class"
/>| Property | Type | Default | Description |
|---|---|---|---|
themes |
Theme[] |
['light', 'dark', 'system'] |
Available themes |
currentTheme |
Theme |
- | Controlled current theme (optional) |
onThemeChange |
(theme: Theme) => void |
- | Theme change callback (optional) |
animationType |
ThemeAnimationType |
ThemeAnimationType.CIRCLE |
Animation type |
duration |
number |
500 |
Animation duration in ms |
className |
string |
- | Additional CSS classes |
Note: When used with
SpacemanThemeProvider,currentThemeandonThemeChangeare automatically handled by the context.
Dropdown selector for color themes.
<ThemeSelector
colorThemes={['default', 'blue', 'green', 'purple']}
currentColorTheme="blue"
onColorThemeChange={colorTheme => console.log(colorTheme)}
animationType={ThemeAnimationType.BLUR_CIRCLE}
duration={400}
/>| Property | Type | Default | Description |
|---|---|---|---|
themes |
Theme[] |
['light', 'dark', 'system'] |
Available themes (for standalone hook) |
colorThemes |
ColorTheme[] |
['default'] |
Available color themes |
currentColorTheme |
ColorTheme |
- | Controlled current color theme (optional) |
onColorThemeChange |
(colorTheme: ColorTheme) => void |
- | Color theme change callback (optional) |
animationType |
ThemeAnimationType |
ThemeAnimationType.CIRCLE |
Animation type |
duration |
number |
500 |
Animation duration in ms |
Note: When used with
SpacemanThemeProvider,currentColorThemeandonColorThemeChangeare automatically handled by the context.
Animation types for theme transitions.
enum ThemeAnimationType {
CIRCLE = 'circle',
BLUR_CIRCLE = 'blur-circle',
}type Theme = 'light' | 'dark' | 'system'
type ColorTheme = string // e.g., 'default', 'blue', 'green', etc.
interface SpacemanThemeProviderProps {
children: React.ReactNode
defaultTheme?: Theme
defaultColorTheme?: ColorTheme
themes?: Theme[]
colorThemes?: ColorTheme[]
animationType?: ThemeAnimationType
duration?: number
blurAmount?: number
onThemeChange?: (theme: Theme) => void
onColorThemeChange?: (colorTheme: ColorTheme) => void
}
interface UseThemeAnimationProps {
themes?: Theme[]
colorThemes?: ColorTheme[]
theme?: Theme
colorTheme?: ColorTheme
animationType?: ThemeAnimationType
duration?: number
blurAmount?: number
onThemeChange?: (theme: Theme) => void
onColorThemeChange?: (colorTheme: ColorTheme) => void
}
interface UseThemeAnimationReturn {
theme: Theme
colorTheme: ColorTheme
switchTheme: (theme: Theme) => Promise<void>
setColorTheme: (colorTheme: ColorTheme) => void
toggleTheme: () => Promise<void>
ref: RefObject<HTMLElement>
}
interface ThemeSwitcherProps {
themes?: Theme[]
currentTheme?: Theme
onThemeChange?: (theme: Theme) => void
animationType?: ThemeAnimationType
duration?: number
className?: string
}
interface ThemeSelectorProps {
themes?: Theme[]
colorThemes?: ColorTheme[]
currentColorTheme?: ColorTheme
onColorThemeChange?: (colorTheme: ColorTheme) => void
animationType?: ThemeAnimationType
duration?: number
}- View Transitions API: Chrome 111+, Edge 111+
- Fallback: All modern browsers with CSS transitions
- Reduced Motion: Respects
prefers-reduced-motionsetting - Framework Support: React 16.8+ (hooks required)
- Animations use the View Transitions API when available
- Optimized for 60fps animations
- Respects
prefers-reduced-motionsetting
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
MIT