Counts the Clouds

Gatsbyにダークモードを追加する
2021.09.13

Gatsby
tailwindcss
Prism

chris-barbalis-uRbRo8R-9CE-unsplash.jpg

gatsby-plugin-dark-modeを追加

% yarn add gatsby-plugin-dark-mode
// gatsby-config.js
module.exports = {
  plugins: ['gatsby-plugin-dark-mode'],
}

公式の通り。

ThemeTogglerを追加

見た目はあとでどうにかするとして、まず公式のcheckboxをヘッダーあたりに入れる。

// ...
import { ThemeToggler } from 'gatsby-plugin-dark-mode'

class MyComponent extends React.Component {
  render() {
    return (
      <ThemeToggler>
        {({ theme, toggleTheme }) => (
          <label>
            <input
              type="checkbox"
              onChange={e => toggleTheme(e.target.checked ? 'dark' : 'light')}
              checked={theme === 'dark'}
            />{' '}
            Dark mode
          </label>
        )}
      </ThemeToggler>
    )
  }
}

tailwindcssのダークモードを有効にする

The media strategy uses the prefers-color-scheme media feature under the hood, but if you’d like to support toggling dark mode manually, you can also use the ‘class’ strategy for more control.

classを指定することで、メディアクエリとマニュアル操作の両方に対応できるらしい。

// tailwind.config.js
module.exports = {
  darkMode: 'class',
  // ...
}

body要素にダークモードを適用する場合はglobal.cssに指定する

html.jsを追加してカスタマイズしてあっても、body要素に対してtailwindcssのdark:バリアントは効かないので、bodyに対してダークモードを適用するにはglobal.cssに指定する。

body.dark {
  @apply bg-gray-900;
}

tailwindcss-typographyにダークモードを適用する

proseクラスが指定されている要素にdark:prose-darkを追加。

<main className="prose-sm md:prose dark:prose-dark"></main>

tailwind.config.jsにvariants.extend.typographytheme.extend.typography.darkを追加。

module.exports = {
  theme: {
    extend: {
      typography: {
        DEFAULT: {},
        dark: {
          css: {
            color: colors.warmGray['50'],
          },
        },
      },
    },
  },
  variants: {
    extend: {
      typography: ['dark'],
    },
  },
}

あとは、必要な要素に対してdark:バリアントを指定したり、あるいは、.darkクラスの子孫要素に対してスタイリングしていく。

Prismのテーマを切り替える

ダークモードのときはsyntax highlightingのテーマも変えたい。

global.cssをSCSSにして、ネストされた @importでダークモード用のテーマを適用する。

まず、SCSSを使えるようにする。

% yarn add sass gatsby-plugin-sass
// gatsby-config.js
module.exports = {
  plugins: [
    'gatsby-plugin-sass',
  ],
};

global.cssをglobal.scssに変更して、ダークモード用のテーマを追加。

.dark {
  @import "prism-themes/themes/prism-hopscotch";
}

このとき、パスに.css拡張子をつけると読み込めないので注意。

In addition to importing .sass and .scss files, Sass can import plain old .css files. The only rule is that the import must not explicitly include the .css extension, because that’s used to indicate a plain CSS @import.

初期表示のときprefered-color-schemeが反映されない

端末をダークモードにして開発サイトにアクセスしてもダークモードに切り替わらない。

プラグインの処理を見ると、localStorageに保存されている状態が優先されているようだ。

void function() {
  window.__onThemeChange = function() {}
  var preferredTheme
  try {
    preferredTheme = localStorage.getItem('theme')
  } catch (err) { }

// ...

var darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
  darkQuery.addListener(function(e) {
    window.__setPreferredTheme(e.matches ? 'dark' : 'light')
  })
  setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light'))
}()

これでもいいんだけど、例えばGitHubなんかはそうなってない気がする。

個人的にはMacのダークモードを自動にしているので、サイトのダークモードもそれに合わせてほしい。

今回は端末の設定に合わせるのが優先で、それが嫌な人は手動で切り替えてね、とする。

gatsby-plugin-dark-modeから、gatsby-ssr.jsとThemeToggler.jsを取り出して自分のsrcに配置。

gatsby-ssr.jsのダークモードの処理を変更する。

  void function() {
    window.__onThemeChange = function() {}
    var preferredTheme
-   try {
-     preferredTheme = localStorage.getItem('theme')
-   } catch (err) { }

    // ...

    window.__setPreferredTheme = function(newTheme) {
      setTheme(newTheme)
-     try {
-       localStorage.setItem('theme', newTheme)
-     } catch (err) {}
    }
    var darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
    darkQuery.addListener(function(e) {
      window.__setPreferredTheme(e.matches ? 'dark' : 'light')
    })
-   setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light'))
+   setTheme(darkQuery.matches ? 'dark' : 'light')
  }()

期待した動作になったので、gatsby-plugin-dark-modeは削除した。

Gatsbyにダークモードを導入することができた。