Color Modes
Learn how to implement and customize dark mode and other color schemes in Aki UI.
Overview
Aki UI provides built-in support for multiple color modes, including light and dark themes. The components automatically adapt to the current color mode using CSS custom properties and Tailwind's dark mode utilities.
- • Automatic Detection: Respects user's system preference
- • Manual Toggle: Programmatic theme switching
- • Persistent Storage: Remembers user's choice
- • SSR Compatible: No flash of unstyled content
Setup
1. Configure Tailwind CSS
Enable dark mode in your Tailwind configuration:
// tailwind.config.js
module.exports = {
darkMode: 'class', // Enable class-based dark mode
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@akitectio/aki-ui/**/*.{js,ts,jsx,tsx}',
],
// ... rest of your config
}
2. Theme Provider (Optional)
Create a theme provider to manage color mode state:
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
type Theme = 'light' | 'dark' | 'system'
interface ThemeContextType {
theme: Theme
setTheme: (theme: Theme) => void
actualTheme: 'light' | 'dark'
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('system')
const [actualTheme, setActualTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
const savedTheme = localStorage.getItem('theme') as Theme
if (savedTheme) {
setTheme(savedTheme)
}
}, [])
useEffect(() => {
const updateTheme = () => {
const isDark = theme === 'dark' ||
(theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
setActualTheme(isDark ? 'dark' : 'light')
document.documentElement.classList.toggle('dark', isDark)
}
updateTheme()
if (theme !== 'system') {
localStorage.setItem('theme', theme)
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
if (theme === 'system') {
mediaQuery.addEventListener('change', updateTheme)
return () => mediaQuery.removeEventListener('change', updateTheme)
}
}, [theme])
return (
<ThemeContext.Provider value={{ theme, setTheme, actualTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider')
}
return context
}
Theme Toggle Component
Create a theme toggle component for users to switch between modes:
'use client'
import { Button } from '@akitectio/aki-ui'
import { useTheme } from './theme-provider'
export function ThemeToggle() {
const { theme, setTheme, actualTheme } = useTheme()
const toggleTheme = () => {
if (theme === 'light') {
setTheme('dark')
} else if (theme === 'dark') {
setTheme('system')
} else {
setTheme('light')
}
}
const getIcon = () => {
if (theme === 'light') return '☀️'
if (theme === 'dark') return '🌙'
return '🖥️'
}
const getLabel = () => {
if (theme === 'light') return 'Light'
if (theme === 'dark') return 'Dark'
return 'System'
}
return (
<Button
variant="outline"
size="sm"
onClick={toggleTheme}
className="gap-2"
>
<span>{getIcon()}</span>
<span>{getLabel()}</span>
</Button>
)
}
Component Implementation
Using Dark Mode Classes
Aki UI components use Tailwind's dark mode utilities. You can extend this pattern in your own components:
// Example component with dark mode support
function MyCard({ children }: { children: React.ReactNode }) {
return (
<div className="
bg-white dark:bg-gray-800
text-gray-900 dark:text-white
border border-gray-200 dark:border-gray-700
shadow-sm dark:shadow-gray-900/20
rounded-lg p-6
">
{children}
</div>
)
}
CSS Custom Properties
For more complex theming, use CSS custom properties:
:root {
--color-background: 255 255 255;
--color-foreground: 0 0 0;
--color-primary: 59 130 246;
--color-border: 229 231 235;
}
.dark {
--color-background: 17 24 39;
--color-foreground: 255 255 255;
--color-primary: 96 165 250;
--color-border: 55 65 81;
}
.my-component {
background-color: rgb(var(--color-background));
color: rgb(var(--color-foreground));
border-color: rgb(var(--color-border));
}
Color Palette
Light Mode
Background
#FFFFFF
Surface
#F9FAFB
Primary
#3B82F6
Text
#111827
Dark Mode
Background
#111827
Surface
#1F2937
Primary
#60A5FA
Text
#F9FAFB
Best Practices
Design Guidelines
- • Contrast: Ensure sufficient contrast in both light and dark modes (WCAG AA: 4.5:1)
- • Consistency: Maintain visual hierarchy across both themes
- • Colors: Don't rely solely on color to convey information
- • Testing: Test all interactions in both color modes
- • Images: Consider different images or filters for dark mode
Implementation Tips
- • Use semantic color names instead of specific values
- • Prefer CSS custom properties for dynamic theming
- • Test with system preference changes
- • Consider reduced motion preferences
- • Provide visual feedback for theme changes
Next.js App Router Setup
For Next.js App Router applications, wrap your app with the theme provider:
// app/layout.tsx
import { ThemeProvider } from '@/components/theme-provider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}