├── .github └── workflows │ └── publish-npm.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── README.MD ├── example ├── .eslintrc.json ├── .gitignore ├── components │ └── DarkModeControls.tsx ├── hooks │ └── useFirstRender.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── public │ └── noflash.min.js └── tsconfig.json ├── package-lock.json ├── package.json ├── src ├── index.tsx ├── noflash.js ├── noflash.min.js └── useLocalStorage.ts └── tsconfig.json /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | # Setup .npmrc file to publish to npm 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: '16.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | - run: npm i 16 | - run: npm publish 17 | env: 18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .github 3 | tsconfig.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } 4 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # @rbnd/react-dark-mode 2 | 3 | Inspired by [use-dark-mode](https://github.com/donavon/use-dark-mode). And after being frustrated by it's shortcomings, `@rbnd/react-dark-mode` was created. 4 | 5 | [![npm version](https://badge.fury.io/js/@rbnd%2Freact-dark-mode.svg)](https://badge.fury.io/js/@rbnd%2Freact-dark-mode) 6 | 7 | ## How it works 8 | 9 | This package will take care of switching between `light`, `dark` and `system` preference. It will also get rid of the annoying flash between dark-light mode you get when opening a webpage. Supports `typescript` out of the box! 10 | 11 | ## Example 12 | 13 | [Demo example project](https://rbnd-react-dark-mode.netlify.app), the source code can be found in [example directory](https://github.com/RBND-studio/react-dark-mode/tree/master/example) of this repository. 14 | 15 | ## Installation 16 | 17 | ```bash 18 | $ npm i @rbnd/react-dark-mode 19 | # Or Yarn 20 | $ yarn add @rbnd/react-dark-mode 21 | # Or pnpm 22 | $ pnpm add @rbnd/react-dark-mode 23 | 24 | ``` 25 | 26 | ## Usage 27 | 28 | ### 1. Add provider to the root of your app 29 | 30 | ```jsx 31 | import { DarkModeProvider } from "@rbnd/react-dark-mode" 32 | 33 | const App = () => { 34 | 35 | // ... 36 | 37 | return ( 38 | 39 | {/* Your other components */} 40 | 41 | ) 42 | } 43 | ``` 44 | 45 | ### 2. Change modes 46 | 47 | ```jsx 48 | import { useDarkMode } from "@rbnd/react-dark-mode" 49 | 50 | const Settings = () => { 51 | const { mode, setMode } = useDarkMode() 52 | 53 | return ( 54 | 59 | ) 60 | } 61 | ``` 62 | 63 | ### 3. Implement styles 64 | 65 | Class name will be applied to the html element. 66 | 67 | ```css 68 | .light-mode { 69 | color-scheme: light; 70 | background-color: white; 71 | color: black; 72 | } 73 | .dark-mode { 74 | /* Don't forget the color-scheme css attribute. This will avoid light scrollbars in dark mode. */ 75 | color-scheme: dark; 76 | background-color: black; 77 | color: white; 78 | } 79 | ``` 80 | 81 | ### 4. Get rid of the flash 82 | 83 | Copy the `noflash.min.js` from `node_modules/@rbnd/react-dark-mode/src/noflash.min.js` to your `public` folder and add it as a script to your ``. 84 | 85 | ```html 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ``` 95 | 96 | --- 97 | 98 | Created by [rbnd.studio](https://rbnd.studio/). Check out [Atmos](https://atmos.style/) our tool for creating professional color palettes, hey it's free! -------------------------------------------------------------------------------- /example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /example/components/DarkModeControls.tsx: -------------------------------------------------------------------------------- 1 | import { useFirstRender } from "@/hooks/useFirstRender"; 2 | import { Mode, useDarkMode } from "@rbnd/react-dark-mode"; 3 | 4 | const modes: Mode[] = ["light", "dark", "system"]; 5 | 6 | export const DarkModeControls = () => { 7 | const { mode, setMode } = useDarkMode(); 8 | const firstRender = useFirstRender(); 9 | 10 | // Do not render anything on the server, otherwise you will get a mismatch between the server and client HTML 11 | if (firstRender) return null; 12 | 13 | return ( 14 |
15 |

16 | Current mode: {mode} 17 |

18 | 19 |
20 | {modes.map((mode) => ( 21 | 24 | ))} 25 |
26 | 27 |

28 | Note: This component does not render on the server, since you cannot read user preference 29 | from the browser cookies on the server. However `noflash.min.js` will take care of loading 30 | the preference as soon as possible and as a result user will see the background of the page 31 | according to his preference for the whole rendering time. 32 |

33 | 34 | 45 |
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /example/hooks/useFirstRender.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useFirstRender = (): boolean => { 4 | const [firstRender, setFirstRender] = useState(true); 5 | 6 | useEffect(() => { 7 | setFirstRender(false); 8 | }, []); 9 | 10 | return firstRender; 11 | }; 12 | -------------------------------------------------------------------------------- /example/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "13.4.2", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0", 15 | "@rbnd/react-dark-mode": "2.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "20.2.1", 19 | "@types/react": "18.2.6", 20 | "@types/react-dom": "18.2.4", 21 | "eslint": "8.40.0", 22 | "eslint-config-next": "13.4.2", 23 | "typescript": "5.0.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { DarkModeProvider } from "@rbnd/react-dark-mode"; 2 | import type { AppProps } from "next/app"; 3 | 4 | export default function MyApp({ Component, pageProps }: AppProps) { 5 | return ( 6 | // Wrap your app with 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /example/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | 4 | export default function Document() { 5 | return ( 6 | 7 | 8 | {/* Make sure your global styles and/or css variables are in the HTML file, not in an external CSS file. */} 9 | 22 | 23 | {/* Noflash script in the of the HTML document */} 24 | {/* eslint-disable-next-line @next/next/no-sync-scripts */} 25 |