pacocoursey / next-themes

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing
https://next-themes-example.vercel.app/
MIT License
5.25k stars 195 forks source link

Support for `theme-color` #78

Open shuding opened 2 years ago

shuding commented 2 years ago

Safari 15 uses this meta tag to indicate the UI color of the address bar etc., and usually we need to switch it upon theme changes. The media query only works with the system preferences rather than per-site settings via next-themes, so it might be a good add-on for this lib to automatically update it.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color

pacocoursey commented 2 years ago

You can set theme-color to a CSS variable, which will update correctly on Safari! On iOS it will update instantly alongside changes to the CSS variable, on macOS it will only update when you refocus the tab.

<meta name="theme-color" content="var(--bg)">

If we were to add it to next-themes (and not use the CSS variable trick above), how we would determine which color to set? Maybe consumers could provide a --theme-color CSS variable, which next-themes would read the current value of (getComputedStyle) and update the meta tag accordingly.

shuding commented 2 years ago

Oh I didn’t know that it works with variables, then it almost solves my problem.

If building this into the lib, I can see a themeColor option with these possible values:

nifte commented 2 years ago

@pacocoursey Using the meta tag method you mentioned doesn't seem to be working in the latest Safari/iOS. I still need to refresh the page to see the browser theme change.

I'm setting the css variable like this (I use class mode with Tailwind):

html { --bg: white; }
html.dark { --bg: black; }

Any suggestions?

heychazza commented 2 years ago

Facing the same thing myself.

pacocoursey commented 2 years ago

Since the CSS variable trick no longer seems to work, I'd recommend implementing this manually in your code with something like this:

const { resolvedTheme } = useTheme();

return (
  <Head>
    <meta name="theme-color" value={resolvedTheme === 'dark' ? '#000' : '#fff'}
  </Head>
)

I plan to add this for v1 of next-themes though, and I like the API that @shuding suggested above!

<ThemeProvider
  themeColor={{ dark: '#000', light: '#fff' }}
  // or
  themeColor="var(--theme-color)" // vars must be on the same element as data-theme is applied, which right is now always html aka :root
  // or
  themeColor={{ dark: 'var(--dark-bg)', light: '#fff' }}
/>
pacocoursey commented 2 years ago

This is available in the v1 beta:

yarn add next-themes@beta
EryouHao commented 1 year ago

Hi @pacocoursey , when v1 release? like this feature.

AdisonCavani commented 1 year ago

@pacocoursey v1 is not published on NPM.

aulianza commented 1 year ago

i solve this problem using this approach:

const { resolvedTheme } = useTheme();

  useEffect(() => {
    let themeColorMeta = document.querySelector(
      'meta[name="theme-color"]',
    ) as HTMLMetaElement;

    if (!themeColorMeta) {
      themeColorMeta = document.createElement('meta');
      themeColorMeta.name = 'theme-color';
      document.head.appendChild(themeColorMeta);
    }

    themeColorMeta.content = resolvedTheme === 'dark' ? '#171717' : '#fff';
  }, [resolvedTheme]);

may this help!

NavOrange commented 1 year ago

For Next.js 13 app router

app/layout.tsx

export const metadata = {
  // ...
  themeColor: '#f8fafc',
}

export default RootLayout() {
  <html suppressHydrationWarning className="dark">
    <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              try {
                if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
                  document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0B1120')
                }
              } catch (_) {}
            `,
          }}
        />
      </head>
  </html>
}

components/toggle-theme.tsx

const { resolvedTheme } = useTheme()

useEffect(() => {
  if (resolvedTheme === 'dark') {
    document
      .querySelector('meta[name="theme-color"]')!
      .setAttribute('content', '#0B1120')
  } else {
    document
      .querySelector('meta[name="theme-color"]')!
      .setAttribute('content', '#f8fafc')
  }
}, [resolvedTheme])

refer https://github.com/tailwindlabs/tailwindcss.com/blob/master/src/pages/_document.js#L31

shawnlong636 commented 1 year ago

Is a fix being worked on for this so we don’t need to add custom logic to ensure automatic color switching on Safari?

trm217 commented 1 year ago

@shawnlong636 Could you elaborate? Do you have the issue with the v1 branch?

alex-grover commented 1 year ago

confirming this is working for me with the 1.0.0-beta.0 release

is there any plan to release that as a full version? happy to help test, it looks like the changes are fairly small

theonlyway commented 1 year ago

Any timeline on when v1 will be released?

spacecat commented 8 months ago

This one did not make it to v0.3.0? 🤔 I'm still targeting the beta in my project ( "next-themes": "1.0.0-beta.0", ) which is not ideal. Hoping to see v1 soon! Or just this particular very needed feature in v0.3.1 😉

pacocoursey commented 8 months ago

Planning to add this in v1, which is still upcoming.

monecchi commented 5 months ago

@pacocoursey Hi there! How to achieve the proposed themeColor solution? I've installed next-themes@beta, current version 0.3.0-beta.1, but it seems <ThemeProvider /> has no themeColor props.

spacecat commented 5 months ago

@monecchi you can do something like this:

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider
      attribute="class"
      themes={[
        "light",
        "dark",
      ]}
      themeColor={{
        light: "hsl(0 0% 100%)",
        dark: "hsl(216 13% 15%)",
      }}
    >
      <SidebarLeftMobileProvider>
        <SidebarRightMobileProvider>
           <UserProvider>{children}</UserProvider>
        </SidebarRightMobileProvider>
      </SidebarLeftMobileProvider>
    </ThemeProvider>
  );
}

I'm using "next-themes": "1.0.0-beta.0",

monecchi commented 5 months ago

Thanks @spacecat! I'll give it a try! It seems the beta version I installed 0.3.0-beta.1 might be the issue...

Also, thanks @NavOrange! I've tested your solution and it worked flawlessly! While developing a PWA with the Window Controls Overlay feature enabled in manifest.json (on Desktop), the window native controls background color inherits the theme-color value, so switching the theme (light / dark) was breaking the UI. That saved my day!

spacecat commented 5 months ago

@spacecat yes, most likely it has got to do with the npm package version.

spacecat commented 2 days ago

Hi @pacocoursey ,

just following up on this issue as I'm currently upgrading all of my npm packages in my project.

I was just wondering if you see this feature included in a stable release some time in the near future?

Thanks!