├── .babelrc ├── .gitignore ├── LICENSE.md ├── README.md ├── gatsby-ssr.js ├── package.json └── src ├── ThemeToggler.js └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["babel-preset-gatsby-package", { "browser": true }]] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /index.js 2 | /ThemeToggler.js 3 | /node_modules/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jonny Buchanan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-plugin-dark-mode 2 | 3 | A Gatsby plugin which handles some of the details of implementing a dark mode theme. 4 | 5 | It provides: 6 | 7 | - Browser code for toggling and persisting the theme (from [Dan Abramov](https://twitter.com/dan_abramov)'s [overreacted.io](https://overreacted.io) implementation) 8 | - Automatic use of a dark mode theme (via the `prefers-color-scheme` [CSS media query](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)) if you've configured your system to use dark colour themes when available. 9 | - A [React](https://reactjs.org) component for implementing theme toggling UI in your site. 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install gatsby-plugin-dark-mode 15 | ``` 16 | 17 | ```js 18 | // gatsby-config.js 19 | 20 | module.exports = { 21 | plugins: ['gatsby-plugin-dark-mode'], 22 | } 23 | ``` 24 | 25 | ## How to use 26 | 27 | ### Implement theme toggling UI 28 | 29 | The plugin module exports a `ThemeToggler` component which takes a `children` [render prop](https://reactjs.org/docs/render-props.html), providing the current `theme` name and a `toggleTheme` function to change the theme. 30 | 31 | Here's an example of using `ThemeToggler` with a checkbox to toggle the theme: 32 | 33 | ```jsx 34 | import React from 'react' 35 | import { ThemeToggler } from 'gatsby-plugin-dark-mode' 36 | 37 | class MyComponent extends React.Component { 38 | render() { 39 | return ( 40 | 41 | {({ theme, toggleTheme }) => { 42 | // Don't render anything at compile time. Deferring rendering until we 43 | // know which theme to use on the client avoids incorrect initial 44 | // state being displayed. 45 | if (theme == null) { 46 | return null 47 | } 48 | return ( 49 | 59 | ) 60 | }} 61 | 62 | ) 63 | } 64 | } 65 | ``` 66 | 67 | The toggled theme will be persisted across visits in `localStorage.theme`. 68 | 69 | ### Implement theming 70 | 71 | The default theme names are `'light'` and `'dark'` - the plugin adds the current theme name to the `` element's `className`, so you can use [global styles](https://www.gatsbyjs.org/docs/creating-global-styles) to implement theming. 72 | 73 | A nice option is to use CSS variables like so: 74 | 75 | ```css 76 | /* global.css */ 77 | 78 | body { 79 | --bg: white; 80 | --textNormal: #222; 81 | --textTitle: #222; 82 | --textLink: blue; 83 | --hr: hsla(0, 0%, 0%, 0.2); 84 | 85 | background-color: var(--bg); 86 | } 87 | 88 | body.dark { 89 | -webkit-font-smoothing: antialiased; 90 | 91 | --bg: darkslategray; 92 | --textNormal: rgba(255, 255, 255, 0.88); 93 | --textTitle: white; 94 | --textLink: yellow; 95 | --hr: hsla(0, 0%, 100%, 0.2); 96 | } 97 | ``` 98 | 99 | You can then use these variables in your site's components... 100 | 101 | ```js 102 | class Layout extends React.Component { 103 | render() { 104 | return ( 105 |
112 | ... 113 |
114 | ) 115 | } 116 | } 117 | ``` 118 | 119 | ...and in your [Typography config](https://www.gatsbyjs.org/docs/typography-js/#creating-the-typography-configuration) if you're using `gatsby-plugin-typography`, which is included in the [Gatsby Starter Blog](https://www.gatsbyjs.org/starters/gatsbyjs/gatsby-starter-blog/): 120 | 121 | ```js 122 | // typography.js 123 | 124 | import './global.css' 125 | 126 | import Typography from 'typography' 127 | import Wordpress2016 from 'typography-theme-wordpress-2016' 128 | 129 | Wordpress2016.overrideThemeStyles = () => ({ 130 | a: { 131 | color: 'var(--textLink)', 132 | }, 133 | // gatsby-remark-autolink-headers - don't underline when hidden 134 | 'a.anchor': { 135 | boxShadow: 'none', 136 | }, 137 | // gatsby-remark-autolink-headers - use theme colours for the link icon 138 | 'a.anchor svg[aria-hidden="true"]': { 139 | stroke: 'var(--textLink)', 140 | }, 141 | hr: { 142 | background: 'var(--hr)', 143 | }, 144 | }) 145 | ``` 146 | 147 | ## Acknowledgements 148 | 149 | The theme detecting/switching/persistence code and the suggested theming implementation are entirely from the [implementation of overreacted.io](https://github.com/gaearon/overreacted.io) by [Dan Abramov](https://twitter.com/dan_abramov) - I'm just publishing them as a plugin to make them easier to use in my own blog, and for reuse by others. 150 | 151 | ## MIT Licensed 152 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | 3 | exports.onRenderBody = function({ setPreBodyComponents }) { 4 | setPreBodyComponents([ 5 | React.createElement('script', { 6 | key: 'gatsby-plugin-dark-mode', 7 | dangerouslySetInnerHTML: { 8 | __html: ` 9 | void function() { 10 | window.__onThemeChange = function() {} 11 | 12 | var preferredTheme 13 | try { 14 | preferredTheme = localStorage.getItem('theme') 15 | } catch (err) { } 16 | 17 | function setTheme(newTheme) { 18 | if (preferredTheme && document.body.classList.contains(preferredTheme)) { 19 | document.body.classList.replace(preferredTheme, newTheme) 20 | } else { 21 | document.body.classList.add(newTheme) 22 | } 23 | 24 | window.__theme = newTheme 25 | preferredTheme = newTheme 26 | window.__onThemeChange(newTheme) 27 | } 28 | 29 | window.__setPreferredTheme = function(newTheme) { 30 | setTheme(newTheme) 31 | try { 32 | localStorage.setItem('theme', newTheme) 33 | } catch (err) {} 34 | } 35 | 36 | var darkQuery = window.matchMedia('(prefers-color-scheme: dark)') 37 | darkQuery.addListener(function(e) { 38 | window.__setPreferredTheme(e.matches ? 'dark' : 'light') 39 | }) 40 | 41 | setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light')) 42 | }() 43 | `, 44 | }, 45 | }), 46 | ]) 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-dark-mode", 3 | "version": "1.1.2", 4 | "description": "A Gatsby plugin which handles some of the details of implementing a dark mode theme", 5 | "main": "index.js", 6 | "files": [ 7 | "*.js" 8 | ], 9 | "keywords": [ 10 | "gatsby", 11 | "gatsby-plugin", 12 | "theming", 13 | "dark-mode" 14 | ], 15 | "scripts": { 16 | "build": "cross-env NODE_ENV=production babel src --out-dir ." 17 | }, 18 | "dependencies": { 19 | "prop-types": "^15.5.7" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.8.4", 23 | "@babel/core": "^7.9.6", 24 | "babel-preset-gatsby-package": "^0.4.1", 25 | "cross-env": "^7.0.2", 26 | "react": "^16.13.1" 27 | }, 28 | "peerDependencies": { 29 | "react": "16.x" 30 | }, 31 | "author": "Jonny Buchanan ", 32 | "homepage": "https://github.com/insin/gatsby-plugin-dark-mode#readme", 33 | "bugs": "https://github.com//insin/gatsby-plugin-dark-mode/issues", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /src/ThemeToggler.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | class ThemeToggler extends React.Component { 5 | state = { 6 | theme: typeof window !== 'undefined' ? window.__theme : null, 7 | } 8 | 9 | componentDidMount() { 10 | window.__onThemeChange = () => { 11 | this.setState({ theme: window.__theme }) 12 | } 13 | } 14 | 15 | toggleTheme(theme) { 16 | window.__setPreferredTheme(theme) 17 | } 18 | 19 | render() { 20 | return ( 21 | 25 | ) 26 | } 27 | } 28 | 29 | ThemeToggler.propTypes = { 30 | children: PropTypes.func.isRequired, 31 | } 32 | 33 | export default ThemeToggler 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ThemeToggler from './ThemeToggler' 2 | 3 | export { ThemeToggler } 4 | --------------------------------------------------------------------------------