-
-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for multiple attributes (more in depth theme management) #236
Comments
Was able to achieve theme + mode with data-theme attribute using this library, but had to make so many changes that I ended up getting rid of next-themes all together. Here's my current implementation on Nextjs 14.2.1 with tailwind, shadcn ui, and zustand: ✅ Perfect dark mode in 2 lines of code theme.tsx - Replacement for 'use client'
import { useThemeStore } from '@/lib/stores/use-theme-store'
import { useEffect } from 'react'
interface ThemeProps {
defaultTheme?: string
defaultMode?: 'light' | 'dark' | 'system'
}
export function Theme({ defaultTheme = 'light', defaultMode = 'system' }: ThemeProps) {
const { system, mode, setMode } = useThemeStore()
// system theme listener
useEffect(() => {
function onChange(event: MediaQueryListEvent | MediaQueryList) {
// !system or !mode or system and ui in same mode
if (!system || !mode || event.matches === (mode === 'dark')) return
// system switched to dark mode, ui in light mode
else if (event.matches) setMode('dark')
// system switched to light mode, ui in dark mode
else if (!event.matches) setMode('light')
}
const result = matchMedia('(prefers-color-scheme: dark)')
result.addEventListener('change', onChange)
return () => result.removeEventListener('change', onChange)
}, [mode, setMode, system])
return (
<script
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `(${setInitialTheme.toString()})(${JSON.stringify({ defaultTheme, defaultMode })})`
}}
/>
)
}
interface setInitialThemeProps {
defaultTheme: string
defaultMode: 'light' | 'dark' | 'system'
}
type Theme = {
state: {
theme: string
mode: 'light' | 'dark'
system: boolean
}
version: number
}
function setInitialTheme({ defaultTheme, defaultMode }: setInitialThemeProps) {
const cache = localStorage.getItem('themeStore')
const defaultSystem = defaultMode === 'system'
const defaultModeResolved = defaultSystem
? matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
: defaultMode
const themeStore: Theme = cache
? JSON.parse(cache)
: {
state: { theme: defaultTheme, mode: defaultModeResolved, system: defaultSystem },
version: 0
}
const theme = themeStore.state.theme
const system = themeStore.state.system
const mode = system
? matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
: themeStore.state.mode
document.documentElement.setAttribute('data-theme', `${mode}_${theme}`)
document.documentElement.style.colorScheme = mode
if (!cache) localStorage.setItem('themeStore', JSON.stringify(themeStore))
} use-theme-store.tsx - Replacement for import { create } from 'zustand'
import { persist } from 'zustand/middleware'
type ThemeStore = {
theme: string | undefined
setTheme: (value: string) => void
mode: 'light' | 'dark' | undefined
setMode: (value: 'light' | 'dark') => void
system: boolean | undefined
setSystem: (value: boolean) => void
}
export const useThemeStore = create(
persist<ThemeStore>(
(set, get) => ({
theme: undefined,
setTheme: (theme) => {
const mode = get().mode!
document.documentElement.setAttribute('data-theme', `${mode}_${theme}`)
document.documentElement.style.colorScheme = mode
set(() => ({ theme: theme }))
},
mode: undefined,
setMode: (mode) => {
const theme = get().theme
document.documentElement.setAttribute('data-theme', `${mode}_${theme}`)
document.documentElement.style.colorScheme = mode
set(() => ({ mode: mode }))
},
system: undefined,
setSystem: (system) => {
const isDark = matchMedia('(prefers-color-scheme: dark)').matches
const mode = isDark ? 'dark' : 'light'
const theme = get().theme!
document.documentElement.setAttribute('data-theme', `${mode}_${theme}`)
document.documentElement.style.colorScheme = mode
set(() => ({ system: system, mode: mode }))
}
}),
{
name: 'themeStore'
}
)
) global.css - Example file. Prefix light mode themes with @tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%; /* white */
--foreground: 240 10% 3.9%; /* zinc-950 */
/* remaining styles */
}
html[data-theme='dark_zinc'] {
--background: 240 10% 3.9%; /* zinc-950 */
--foreground: 0 0% 98%; /* zinc-50 */
/* remaining styles */
}
html[data-theme='light_slate'] {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
/* remaining styles */
}
html[data-theme='dark_slate'] {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
/* remaining styles */
}
} tailwind.config.ts - Add import type { Config } from 'tailwindcss'
const config = {
darkMode: ['selector', '[data-theme^="dark"]'],
content: ['./components/**/*.{ts,tsx}', './app/**/*.{ts,tsx}'],
prefix: '',
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
// remaining config
},
// remaining config
}
}
} satisfies Config
export default config To change the theme, mode or system settings, you can do something like this: const { theme, setTheme, mode, setMode, system, setSystem } = useThemeStore()
setTheme('slate')
// or
setMode('dark')
// or
setSystem(false) |
I think this could easily be solved by simply allowing multiple ThemeProviders to exists. |
It would be great if we could manage multiple attributes controlling different aspects of the theming separately.
An example could be a theme controller component that lets you separately choose the theme (meaning the color palette) and the mode (light, dark o system).
Look shadcn.ui/themes for instance.
It would be enough having the possibility to declare two different attributes (for instance [data-theme="twitter"] and [data-mode="system"]) on the html element to have them both.
This would make building themable web apps and websites using next.js far more easy.
The text was updated successfully, but these errors were encountered: