Comments (2)
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
✅ System setting with prefers-color-scheme
✅ Themed browser UI with color-scheme
✅ Support for Next.js 13 appDir
✅ No flash on load (both SSR and SSG)
✅ Sync theme across tabs and windows
❌ Disable flashing when changing themes - check disableAnimation function to implement
❌ Force pages to specific themes - not implemented
❌ Class or data attribute selector - only data-theme
✅ useTheme hook
❌ Other things missing as well, but this is the general gist
theme.tsx - Replacement for <ThemeProvider> <ThemeProvider />
. Add this to your layout.tsx
, then set defaultTheme & defaultMode.
'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 useTheme()
.
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 light_
, and dark mode themes with dark_
.
@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 ['selector', '[data-theme^="dark"]']
to darkMode.
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)
from next-themes.
I think this could easily be solved by simply allowing multiple ThemeProviders to exists.
If we used a factory for creating the ThemeProvider, we could look into generating unique theme-context, and thus handle many different theming requirements within the same page all with next-themes. Might be interesting for v1.
from next-themes.
Related Issues (20)
- How to disable syncing themes between tabs HOT 2
- Documentation out of date with Tailwind's new dark-mode API
- Should `resolvedTheme` obey `forcedTheme`? HOT 3
- Allow nested providers HOT 2
- Theme object is injected into the "data-theme" property. HOT 1
- useTheme doesn't work in monorepo ui library. HOT 1
- `TypeError: Cannot read properties of undefined (reading 'addListener')` using `vitest`/`@testing-library/react` HOT 1
- Support for changing both class and data-theme attribute simultaneously HOT 1
- Callback Version of `setTheme` Passes Itself HOT 1
- Possibility to share across sub-domains? HOT 1
- ThemeProvider returns multiple types HOT 3
- setTheme not working HOT 1
- Theme flashing v0.3.0 HOT 7
- Next.js suspense triggered twice with next-themes HOT 1
- Flash Of Unstyled Content (FOUC) on Live Website HOT 6
- The way avoid hydration errors has a negative impact on SEO. HOT 2
- [Feature request]:setTheme multiple classes for next-themes HOT 5
- [Feature request]: The opportunity not to store theme value in LS
- [Feature request]: react 19 support HOT 2
- [Bug]: NextJs dark mode background is black on overscroll
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from next-themes.