Giter Club home page Giter Club logo

use-dark-mode's Introduction

use-dark-mode

A custom React Hook to help you implement a "dark mode" component for your application. The user setting persists to localStorage.

❤️ it? ⭐️ it on GitHub or Tweet about it.

npm version Build Status All Contributors Tweet

usedarkmode-small

useDarkMode works in one of two ways:

  1. By toggling a CSS class on whatever element you specify (defaults to document.body). You then setup your CSS to display different views based on the presence of the selector. For example, the following CSS is used in the demo app to ease the background color in/out of dark mode.

    body.light-mode {
      background-color: #fff;
      color: #333;
      transition: background-color 0.3s ease;
    }
    body.dark-mode {
      background-color: #1a1919;
      color: #999;
    }
  2. If you don't use global classes, you can specify an onChange handler and take care of the implementation of switching to dark mode yourself.

New in Version 2.x

  • useDarkMode now persists between sessions. It stores the user setting in localStorage.

  • It shares dark mode state with all other useDarkMode components on the page.

  • It shares dark mode state with all other tabs/browser windows.

  • The initial dark mode is queried from the system. Note: this requires a browser that supports the prefers-color-scheme: dark media query (currently Chrome, Firefox, Safari and Edge) and a system that supports dark mode, such as macOS Mojave.

  • Changing the system dark mode state will also change the state of useDarkMode (i.e, change to light mode in the system will change to light mode in your app).

  • Support for Server Side Rendering (SSR) in version 2.2 and above.

Requirement

To use use-dark-mode, you must use [email protected] or greater which includes Hooks.

Installation

$ npm i use-dark-mode

Usage

const darkMode = useDarkMode(initialState, darkModeConfig);

Parameters

You pass useDarkMode an initialState (a boolean specifying whether it should be in dark mode by default) and an optional darkModeConfig object. The configuration object may contain the following keys.

Key Description
classNameDark The class to apply. Default = dark-mode.
classNameLight The class to apply. Default = light-mode.
element The element to apply the class name. Default = document.body.
onChange A function that will be called when the dark mode value changes and it is safe to access the DOM (i.e. it is called from within a useEffect). If you specify onChange then classNameDark, classNameLight, and element are ignored (i.e. no classes are automatically placed on the DOM). You have full control!
storageKey A string that will be used by the storageProvider to persist the dark mode value. If you specify a value of null, nothing will be persisted. Default = darkMode.
storageProvider A storage provider. Default = localStorage. You will generally never need to change this value.

Return object

A darkMode object is returned with the following properties.

Key Description
value A boolean containing the current state of dark mode.
enable() A function that allows you to set dark mode to true.
disable() A function that allows you to set dark mode to false.
toggle() A function that allows you to toggle dark mode.

Note that because the methods don't require any parameters, you can call them direcly from an onClick handler from a button, for example (i.e., no lambda function is required).

Example

Here is a simple component that uses useDarkMode to provide a dark mode toggle control. If dark mode is selected, the CSS class dark-mode is applied to document.body and is removed when de-selected.

import React from 'react';
import useDarkMode from 'use-dark-mode';

import Toggle from './Toggle';

const DarkModeToggle = () => {
  const darkMode = useDarkMode(false);

  return (
    <div>
      <button type="button" onClick={darkMode.disable}></button>
      <Toggle checked={darkMode.value} onChange={darkMode.toggle} />
      <button type="button" onClick={darkMode.enable}></button>
    </div>
  );
};

export default DarkModeToggle;

That flash!

If your CSS is setup to default to light mode, but the user selects dark mode, the next time they visit your app, they will be in dark mode. However, the user will see a flash of light mode before the app is spun up and useDarkMode is called.

To prevent this, I've included some vanilla JavaScript that you can insert in your index.html just after the <body> tag. It is in a file named noflash.js.txt. You can either insert the contents of this file in a <script> tag or automate the step in your build process.

Note that if you change any of the default—such as storageKey or classNameDark for example—the noflash.js file will need to be modified with the same values.

Gatsby

Gatsby users may leverage gatsby-plugin-use-dark-mode to inject noflash.js for you.

Next.js

For next.js uses copy the noflash.js.txt to your public folder (public/noflash.js) and then create a _document.js and include the script before <Main />.

import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <script src="noflash.js" />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Sample Apps

Here is a list of apps build with use-dark-mode. If you have an app you would like to include on this list, open a PR.

License

MIT Licensed

Contributors

Thanks goes to these wonderful people (emoji key):


Donavon West

🚇 ⚠️ 💡 🤔 🚧 👀 🔧 💻

Revel Carlberg West

🤔

Mateusz Burzyński

💻

Justin Hall

💻

Jeremy

📓 🐛

Janosh Riebesell

📖

Andrew Lisowski

📖

Jorge Gonzalez

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

use-dark-mode's People

Contributors

allcontributors[bot] avatar andarist avatar dependabot[bot] avatar donavon avatar hipstersmoothie avatar janosh avatar jorgegonzalez avatar wkovacs64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

use-dark-mode's Issues

Requires 2 clicks when reload on darkMode.value true

Hi, i'm using gatsby here. I found out that the toggle didn't work well with <input> onChange. It requires 2 clicks for state changes. This behaviour occurs when we reload the page with darkMode.value true

Here is the component

import * as React from 'react';
import useDarkMode from 'use-dark-mode';

const DarkmodeToggle = () => {
    
    const { toggle, value } = useDarkMode(false)

    return (
        <div>
            <label>
                <input
                    type="checkbox"
                    checked={value}
                    onChange={toggle}
                />  
            </label>
        </div>
    )
}

export default DarkmodeToggle

anyone here have the same issue?

App crashing when opening two tabs

What Happened

There is an error message in the console:

Uncaught SyntaxError: Unexpected token o in JSON at position 1
    at Object.parse (<anonymous>)
    at Object.current (use-persisted-state.m.js:52)
    at n (event-listener.m.js:10)

I also put a console.log in node_modules/use-persisted-state and it seems to be parsing null as a JSON string.

How to reproduce

  1. Go to the sample app provided in the README: https://mzj64x80ny.csb.app/
  2. Open the same app on another tab and toggle the dark mode.
  3. Go back to the first opened tab and check the console.

Doesnt work correctly with NextJS

I am trying to use this with Next JS in _app.js but it seems that despite SSR support, there is some issue. Mechanism:

  • Fetch from System/Local Storage on first server render (SSR) in _App.js
  • "Value" argument from useDarkMode is passed to a theme switcher
  • in other components, i use "enable" and "disable" from useDarkMode to switch between light and dark mode
  • "Value" doesn't change. Need to refresh page two times or more in order to make it work

No Error in console. It just doesn't change useDarkMode "value" prop no matter how many times i enable() or disable().

  • Windows 10
  • useDarkMode latest version
  • Next JS 10
  • Node JS 14.15

Loosing dark mode when switching pages

Thanks for this nice component!

I followed the example in the Readme and it its working on my Gatsby site within one page. However, when I switch pages, dark mode is not persisted.

I think the problem may be that my toggle component is regenerated on every page switch. But shouldn't the current value be read from localStorage?

Could you please give some guidance?

Flashing with styled-components

I am using styled-components and use-dark-mode with default config, both the plugin and the hook itself.
When dark mode is enabled and I refresh the page I still get colors flashing.

I have a Layout.tsx component that includes:

const GlobalStyles = createGlobalStyle`
    body {
        transition: all 0.5s linear;

        &.dark-mode {
          background-color: #1f1f1f;
          color: #dfe6e9;
        }

        &.light-mode {
          background-color: #ecf0f1;
          color: #1f1f1f;
        }
    }
`

which is then included in the layout component:

export const Layout = ({ children }) => {
    <div>
        <GlobalStyles />
        {children}
    </div>
}

Not support 17 version of react

I try install package, but this give me error:
npm ERR! node_modules/react
npm ERR! react@"17.0.2" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0" from [email protected]
npm ERR! node_modules/use-dark-mode
npm ERR! use-dark-mode@"*" from the root project

Works on development but fails on production in Gatsby

I've been trying to fix this issue but couldn't. I'm using use-dark-mode with gatsby and while developing the application it works fine, but when I run build of the application it shows wrong behaviour, On page refresh the icon changes from the previous value even though the mode (dark/light persists)

darkmode
I've added the your custom js in my html but doesn't seem to work.
Here's my implimentation

export default function Navbar({ title }) {
  const darkMode = useDarkMode()
  return (
    <nav className="nav">
      <Container>
        <div className="nav__content">
          <Link to="/" className="nav__brand">
            {darkMode.value ? "Dark" : "Light"}
          </Link>
          <div className="nav__right">
            
            {darkMode.value === true ? (
              <Sun disable={darkMode.disable} />
            ) : (
              <Moon enable={darkMode.enable} />
            )}
            
          </div>
        </div>
      </Container>
    </nav>
  )
}

This is how I style elements

body.light-mode nav {
  background: rgb(153, 153, 153);
  color: black;
}

body.dark-mode nav {
  background: $dark-mode-section;
}

Thanks for your help

Query initial state synchronously

Since we can both read from localStorage and find the media query value synchronously at startup, the flash situation could be avoided if there's no server-side rendering or static site generation. I would love to implement this, and would like to know the maintainers' thoughts about it.

React version in peerDependencies

Do you think we can expand the React version in peerDependencies? 16.8.1 is out, for example. Maybe ^16.8.0 as the hooks used should be stable and follow semver now? Not sure exactly what would be best.

webpack build error: document is not defined

I am planning to add dark mode for my website and import this package, it works in development but when building it throws an error: document is not defined. I tried to pass the optioalConfigObject but still get the error.

Work on all stylesheets

[FEATURE REQUEST]

It seems like, currently, this hook only works with global styles sheets but as the codebase grows, multiple stylesheets become a necessity. This is particularly relevant with frameworks like Next.js where the file structure advises different stylesheets for different modules, separate from the global stylesheet.

I think it'll be really valuable if this worked with all stylesheets and not just global. Since the useDarkMode function looks for classes name .dark-mode or .light-mode (or other user-defined names) could this hook be used to also implemented these classes present in other stylesheets?

Dark mode preference doesn't apply when using "Auto" system preference

This hooks is great, I've recently implemented it on my site https://brianlovin.com. I've noticed that if I want the dark mode setting to be entirely based on the user's OS (e.g. I don't include a toggle on the page), that things break if the user is using the automatic dark mode setting on their OS.

Bug:

  • set your macOS / iOS preferences to turn on dark mode at sunset, off at sunrise
  • open any app (or my site) before sunset - you should see day mode
  • open again after sunset - you will still see day mode

I'm wondering if there could be a refetchUserPreference method or something that is exposed from the hook which I could run in my own code in an effect or just on-mount that would help people who visit the site between OS setting switches.

Is there any way to implement useDarkMode config static or server-side? (NextJS)

I'm trying to implement the configuration for useDarkMode hook, it works when NextJS uses CSR, but has an error when renders in server-side.
Any way to implement that configuration in SSG or SSR?
Also i want to use the element for <html> anchor tag, because of using Tailwind CSS, so it's crucial for me to implement element attribute.

My code:

function MyApp({ Component, pageProps }: AppProps) {
  useEffect(() => {
    // @ts-ignore
    const darkMode = useDarkMode(false, {
      classNameDark: "dark",
      classNameLight: "light",
      element: document.getElementsByTagName("html")[0]
    });
  }, [])


  return (
    <Fragment>
      <Head>
        <title>Oficjalna strona Nostalgawki</title>
        <meta name="viewport" content="initial-scale=1, width=device-width"/>
      </Head>
      <div className={"main"}>
        <AnimatePresence exitBeforeEnter>
            <Navbar key={"navbar"}/>
            <Component key={"component"} {...pageProps} />
            <Footer key={"footer"}/>
        </AnimatePresence>
      </div>
    </Fragment>
  );
}

Is it maintained?

Is this project still maintained? The last publish was 3 years ago and the peer dependencies are failing with the version 17+ of React.

Are you looking for new maintainers or to give the projet to other developers?

Average time to resolve an issue

Average time to resolve an issue

Percentage of issues still open

Percentage of issues still open

noflash.js install procedure

Can you explain why noflash.js needs to be installed differently and is not able to be built in as part of the package?

In my environment I have to polyfill media queries so it will make me include those in the index.html as well rather than just a standard import in my create-react-app.

I was hoping for a way around this. Thanks

Errors with SSR on Next.js

Sometimes, the value on the server doesn't match the one on the client (when doing SSR).

Warning: Prop `aria-label` did not match. Server: "Dark Mode" Client: "Light Mode"

I did include noflash.js.

redux? context?

not so much an issue but wondering if anyone has a spinning example with this utilised?

Trying to access this global state but only className seems to change on body, no prop or state.. would be good to have something like this globally accessible

Changes to system preference made when site is not open are not honoured

The useDarkMode hook can handle changes to system dark/light mode preference if the website is open during the preference change. However if the system's dark/light mode preference is changed when the user is not visiting the website it will remain in the previous state instead of updating. This happens even if the original preference came from the system setting and the user never interacted with a toggle suggesting a preference other than what their system declares.

How to test

  • Set your system to light mode
  • Open up a page with useDarkMode (after first clearing localStorage to ensure there is no prior darkMode setting)
  • Close the page
  • Change your system to dark mode
  • Reopen the page
  • The page will remain in light mode even though your system is in dark mode

Practical

This creates two problematic scenarios.

  1. A user who visits a bunch of websites that use use-dark-mode, then months later decides they want a system wide dark mode and enables it, then visits a bunch of websites that use use-dark-mode. The new sites they visit will be in dark mode while the sites they visited months ago will be in light mode.

  2. Say a user uses f.lux's "Dark theme at sunset" setting, or iOS' "Light Until Sunset", or Android's "Dark theme: Schedule" setting. If they visit a website and it becomes sunset while they are visiting the site, it will switch to dark mode as the system switches. However if the user visits a website during the day, finishes, then next day visits the site at night the site will use the light theme even though it's night, the system is in dark mode, and the site would otherwise have been in dark mode.

System query causes initial mode to ignore initialState value

System query causes initial mode to ignore initialState value — is this a feature or a bug?

If a site prioritizes a particular mode by setting initialState then system preference would be ignored. The user would have to manually change the site preference, otherwise if initialState is null, then the system preference is used.

can we defer noflash.js?

attempting to optimise and wondering if we can defer this script and still get the positive effect it achieves?

Gatsby v3 requires react > 17

Hello maintainers, looks like react 17 has become a dependency of Gatsby (V3). Any way we can get updated? Love the hook!

Dark mode flashes in Next.js even with noflash.js

The dark mode on my Next.js app still flashes even with adding noflash.js. I have made a dark mode component like so:

import useDarkMode from "use-dark-mode";

import { FiMoon } from "react-icons/fi";

const DarkModeToggle = () => {
  const darkMode = useDarkMode(false);

  return (
    <div className="custom-control custom-switch">
      <input
        type="checkbox"
        className="custom-control-input"
        id="darkmodeSwitch"
        onChange={darkMode.toggle}
        checked={darkMode.value}
        onClick={darkMode.disable}
      />
      <label className="custom-control-label" htmlFor="darkmodeSwitch">
        <FiMoon />
      </label>
    </div>
  );
};

export default DarkModeToggle;

Added noflash.js to the public folder.
Then on _document.js

import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <script src="noflash.js" />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

In my SCSS folder I created a simple _dark-mode.scss and added styles for dark mode in there e.g.:

body.light-mode {
  transition: background-color 0.3s ease;
}
body.dark-mode {
  background-color: #121212;
  color: #f5f5f5;
  transition: background-color 0.3s ease;
  img {
    opacity: 0.8;
  }
}

dark_mode_flash
As you can see it flashes and there's also the toggle/switch that is not persistent.

Check Box toggle delay

Even tho the flashScript fixes the white flash, when the user reloads the page and he has selected the dark mode previously, he will see the checkbox toggle for a brief second on light mode. Only after some ms it goes to the dark mode side.

Any way to fix this?

bug

hi
unnecessary useEventListener() in useDarkMode.js is found
please fix it

Invalid initialValue will crash app even after a valid value is provided

I can get this to work nicely in Chrome and Chromium-based browsers, but it seems use-persisted-state is having an issue with Firefox and crashing the entire app.

SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
get
node_modules/use-persisted-state/dist/use-persisted-state.m.js:29

  26 | return {
  27 |   get: function (n, e) {
  28 |     var r = t.getItem(n);
> 29 |     return null === r ? "function" == typeof e ? e() : e : JSON.parse(r);
     | ^  30 |   },
  31 |   set: function (n, e) {
  32 |     t.setItem(n, JSON.stringify(e));

./node_modules/use-persisted-state/dist/use-persisted-state.m.js/</__webpack_exports__.default/</</s<
node_modules/use-persisted-state/dist/use-persisted-state.m.js:43

  40 |     f = o.set,
  41 |     l = e(null),
  42 |     s = t(function () {
> 43 |   return a(i, u);
     | ^  44 | }),
  45 |     v = s[0],
  46 |     g = s[1];

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.