Skip to content

Add option to opt-out of automatic script insertion #353

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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

plmrry
Copy link

@plmrry plmrry commented Apr 17, 2025

Problem

We've seen a bug in Chrome where (in dark mode) there is a flash of light-themed code.

This appears to be due to how next-themes inserts <ThemeScript /> into the <body />

Solution

This PR:

  1. Adds an optional withScript prop to <Theme>.
  • This allows the user to optionally disable the insertion of <ThemeScript> by setting withScript to false.
  • withScript defaults to true, which preserves the default behavior of next-themes.
  1. Exports <ThemeScript>, so it can be manually inserted by the user (i.e. in the <head>)

  2. Adds an optional string id prop that gets added to the script itself

Example

This is pseudo-code:

import { ThemeProvider, ThemeScript } from 'next-themes'

export default function RootLayout({
  children
}): JSX.Element {
  return (
    <html>
      <head>
        <ThemeScript storageKey="my-theme" />
      </head>
      <body>
        <ThemeProvider storageKey="my-theme" withScript={false}>
        {children}
      </body>
    </html>
  )
}

Based on internal work from @cramforce:

This appears to fix a long-standing bug where you get a flash of light-theme in Chrome.

The primary change in the fork is to allow placing the inline script manually. With the default-usage of next-themes, the script always goes into the body (usually first).

@plmrry plmrry changed the title Work In Progress: Opt-out of script insertion Add option to opt-out of automatic script insertion Apr 17, 2025
@plmrry plmrry marked this pull request as ready for review April 17, 2025 19:14
@plmrry plmrry marked this pull request as draft April 17, 2025 19:42
@plmrry plmrry marked this pull request as ready for review April 17, 2025 19:53
@@ -32,6 +32,7 @@ export const ThemeProvider = (props: ThemeProviderProps) => {
const defaultThemes = ['light', 'dark']

const Theme = ({
withScript = true,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new prop that allows us to bypass mounting the script when set to false

}}
/>

{withScript ? (
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's where withScript determines if we should mount the script

{children}
</ThemeContext.Provider>
)
}

const ThemeScript = React.memo(
export const ThemeScript = React.memo(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export ThemeScript for manual placement

@plmrry plmrry requested a review from styfle April 27, 2025 12:40
Copy link
Contributor

@styfle styfle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this id necessary? We already have scriptProps

@plmrry
Copy link
Author

plmrry commented Apr 27, 2025

@styfle I removed that unnecessary id prop and added a unit test

Comment on lines +80 to +81
document.documentElement.removeAttribute('data-theme-test')
document.documentElement.removeAttribute('data-example')
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is un-related, but I noticed these need to be removed

@plmrry plmrry requested a review from styfle April 27, 2025 16:02
Copy link
Contributor

@styfle styfle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! 🎉

@pacocoursey Can you take another look?

@plmrry plmrry requested a review from styfle April 28, 2025 19:24
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, the PR looks quite succinct now. However I'm not sure this is a good approach – you're basically adding a feature that allows consumers to shoot themselves in the foot and not have perfect dark mode as this library advertises.

Could you elaborate on the issues you experienced with flashing on Chrome? Is this due to the script not being injected in the <head> element? If so, ideally the library can be fixed to inject into the head instead by using next/link with beforeHydration option. This way everyone that uses it will have correct behavior out of the box –– the whole point of this library.

In the past, next/script was quite buggy and this option didn't work reliably. But that was 2+ years ago so maybe it's viable to do it again.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback.

To be honest, this PR is just relaying work by @cramforce that was done internally. I need to dig into the original bug a bit more.

If so, ideally the library can be fixed to inject into the head instead by using next/link with beforeHydration option.

Would this mean adding next as a dependency? Happy to do this if that's what you mean.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, it'd be helpful to know more about the root issue. And yes, we can bring back next as a peerDep. It is next-themes, after all.

But we could also offer the option to export the script standalone as this PR already does.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pacocoursey I'm working on a repro of the original issue, but in the meantime I created a tiny PR to export <ThemeScript>:

#355

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants