picocss / picocss.com

Pico CSS documentation
https://picocss.com
Other
17 stars 21 forks source link

Light/Dark mode FOUC on all pages #16

Open FireIsGood opened 4 months ago

FireIsGood commented 4 months ago

Currently, there is a Flash of Unstyled Content on all pages when the user has the color scheme opposite to their browser preference—prefers light with dark mode or prefers dark with light mode.

This is seen where the browser flashes dark/light before correcting itself.

Example:

https://github.com/picocss/picocss.com/assets/109556932/a3098d0e-70d4-4bfa-835d-bfb48d21aac4

To fix this, you would need to somehow set the HTML attribute before the page is loaded. As posted in a related issue in the examples repository, this can be solved with an inlined script checking the user's preference and/or localstorage theme selection in the head tag.

I am not skilled at Remix styling, however I have made a quick patch fix to show what this could look like:

https://github.com/picocss/picocss.com/assets/109556932/acfad323-4a8f-4b15-b8f0-727ebb5812ea

The specific code for this is VERY hacky, however it may be helpful for making a better implementation. The following code was placed in /app/components/Head.jsx at the bottom of the <head> tag:

      <script
        dangerouslySetInnerHTML={{
          __html: `
// This must be inline to stop FOUC

const localStorageKey = "picoColorScheme";
const rootAttribute = "data-theme";

const scheme = (() => {
  // Check for a stored theme
  if (typeof localStorage !== "undefined" && localStorage.getItem(localStorageKey) !== undefined) {
    return localStorage.getItem(localStorageKey);
  }

  // Otherwise, check the user's color scheme preference
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
})();

// Set a data attribute for Pico
document.querySelector("html")?.setAttribute(rootAttribute, scheme.split('"').at(1));
`,
        }}
      ></script>

A better solution is likely using something like the <Script> functionality to somehow get type safe code pasted into the head before the page loads, however I am not experienced in either React or Remix specifically.

Additionally, the script has to do weird things like scheme.split('"').at(1) since the existing local storage is set to the string of "dark" instead of just dark. I'm not sure if this is to help with the theme changer or JSX as a whole, but this caused a weird bug when I was trying to run the code.