@space-man/react-theme-animation
v2.0.0
Published
React theme switching with smooth view transition animations and multi-theme support
Downloads
1,544
Maintainers
Readme
@space-man/react-theme-animation
https://github.com/user-attachments/assets/2819cf70-c474-478e-b821-6b26457c8d4a
React theme switching with smooth view transition animations, multi-theme support, and synchronized state management.
Spaceman React Theme Animation version 2
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>
)
}Live Demo
Features
- 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
Installation
npm install @space-man/react-theme-animationUsage Patterns
This 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.
1. Hook-Only Usage For Light and Dark mode animation
Perfect for custom theme toggle buttons and complete control over animated theme logic.
Basic Theme Toggle
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>
)
}2. Provider Pattern (Recommended)
The most powerful pattern using SpacemanThemeProvider for centralized theme state management. This prevents state drift and provides synchronized animations.
Basic Provider Setup
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>
)
}3. ViteThemeProvider (Vite React SPAs)
Specialized theme provider optimized for Vite React single-page applications. Provides essential theme management without animation features.
Basic Vite Setup
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>
)
}ViteThemeProvider Props
| 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 |
Defining Application color theme as CSS Variables
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);
}API Reference
SpacemanThemeProvider
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>SpacemanThemeProvider Props
| 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 |
useSpacemanTheme
Hook to access theme state from SpacemanThemeProvider context.
const { theme, colorTheme, switchTheme, setColorTheme, switchThemeFromElement, ref } =
useSpacemanTheme()Returns
| 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 |
useThemeAnimation
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),
})Options
| 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 |
Return
| 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 |
ThemeSwitcher
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"
/>ThemeSwitcher Props
| 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.
ThemeSelector
Dropdown selector for color themes.
<ThemeSelector
colorThemes={['default', 'blue', 'green', 'purple']}
currentColorTheme="blue"
onColorThemeChange={colorTheme => console.log(colorTheme)}
animationType={ThemeAnimationType.BLUR_CIRCLE}
duration={400}
/>ThemeSelector Props
| 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.
ThemeAnimationType
Animation types for theme transitions.
enum ThemeAnimationType {
CIRCLE = 'circle',
BLUR_CIRCLE = 'blur-circle',
}Types
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
}Browser Support
- 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)
Performance
- Animations use the View Transitions API when available
- Optimized for 60fps animations
- Respects
prefers-reduced-motionsetting
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
MIT
