Git Product home page Git Product logo

Comments (2)

ajayvignesh01 avatar ajayvignesh01 commented on June 12, 2024

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.

trm217 avatar trm217 commented on June 12, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.