styled-components でダークモード
styled-components でダークモードとライトモードを実現する方法について順を追ったメモ。
CSS変数とGlobalStyle
ダークモード又はライトモードとして設定された色をCSSで使うためには次のようにCSS変数を介しておこないます。
color: var(–main-fg-color);
それぞれの色の設定は、次のように createGlobalStyleというヘルパー関数を用いて、グローバルに使用可能なCSSを設定できる特別GlobalStyleを生成します。
// src\styled\Global.js
import { createGlobalStyle } from 'styled-components';
const isDarkMode = false; // この値は後でコードから設定するとして・・・
export const GlobalStyle = createGlobalStyle`
:root {
--main-bg-gradient1: ${isDarkMode ? '#3D0529' : '#d4d3dd'};
--main-bg-gradient2: ${isDarkMode ? '#7B103E' : '#efefbb'};
--main-fg-color: ${isDarkMode ? '#fff' : '#333'};
--card-bg-color: ${isDarkMode ? '#15232D' : '#fff'};
--card-textarer-bg-color: ${isDarkMode ? '#193549' : '#eee'};
}
この設定を使えるようにするために、例えば次のようにGlobalStyleコンポーネントを使います。
// src\App.jsx
:
import styled from 'styled-components';
import { GlobalStyle } from './styled/Global';
:
function App() {
return (
<Router>
<GlobalStyle />
:
なんか、ダークモード/ライトモードの切り替えならこれでも十分な気がします。
ThemeProviderを使う方法
こちらは先程のGlobalStyleに加えて、ThemeProviderを使う方法です。ダークモードとライトモードのそれぞれの色を先程はGlobalStyleから得ていましたが、これをテーマ(ダークテーマとライトテーマ)から得るようにします。つまり、色の設定を各テーマに集める方法です。テーマが2種類でなく、もっと沢山あるような場合は扱いやすくなるとは思います。
まずはテーマごとの色設定を別ファイルに移動します。
// src\styled\Themes.js
const sharedStyles = {
// 全テーマ共通設定
buttonBgColor: '#34d5df',
buttonBgHoverColor: '#cb43f5',
};
export const darkTheme = {
mainBgGradientColor1: '#3D0529',
mainBgGradientColor2: '#7B103E',
mainFgColor: '#fff',
cardBgColor: '#15232D',
cardTextarerBgColor: '#193549',
...sharedStyles,
};
export const lightTheme = {
mainBgGradientColor1: '#d4d3dd',
mainBgGradientColor2: '#efefbb',
mainFgColor: '#333',
cardBgColor: '#fff',
cardTextarerBgColor: '#eee',
...sharedStyles,
};
そして、前述の Global.js は次のように変更します。変数 isDarkMode は不要になりました。
// src\styled\Global.js
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
:root {
--main-bg-gradient-color1: ${(props) => props.theme.mainBgGradientColor1};
--main-bg-gradient-color2: ${(props) => props.theme.mainBgGradientColor2};
--main-fg-color: ${(props) => props.theme.mainFgColor};
--card-bg-color: ${(props) => props.theme.cardBgColor};
--card-textarer-bg-color: ${(props) => props.theme.cardTextarerBgColor};
}
前述の App.js では、ThemeProvider で囲み、テーマの初期値を設定します。
// src\App.jsx
:
import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from './styled/Global';
import { lightTheme, darkTheme } from './styled/Themes';
:
function App() {
const theme = 'light'; // この値は後でコードから設定するとして・・・
const currentTheme = theme === 'light' ? lightTheme : darkTheme;
return (
<Router>
<ThemeProvider theme={currentTheme}>
<GlobalStyle />
:
</Router>
);
}
テーマ切り替えのフックとその使用
テーマを切り替えるため、次のようなフックを作成します。
// src\hooks\useTheme.js
import { useState } from 'react';
export default () => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
if (theme === 'light') {
setTheme('dark');
} else {
setTheme('light');
}
};
return [theme, toggleTheme];
};
ただし、これはコンポーネント間で共有されるステート(グローバルステート)ではありませんので、下のように toggleTheme を props でApp から Navbar コンポーネントに渡します。
// src\App.jsx
:
import useTheme from './hooks/useTheme';
:
function App() {
const [theme, toggleTheme] = useTheme(); <--- ★このコンポーネントだけのステート
const currentTheme = theme === 'light' ? lightTheme : darkTheme;
return (
<Router>
<ThemeProvider theme={currentTheme}>
<GlobalStyle />
<StyledApp>
<Navber toggleTheme={toggleTheme} /> <--- ★下位コンポーネントに関数を渡す
Navbarコンポーネントにテーマ切り替えボタンを次のように追加しました。
// src\components\Navbar.jsx
:
import useTheme from '../hooks/useTheme';
:
const Navbar = ({toggleTheme}) => {
:
return (
:
<button onClick={toggleTheme}>テーマ切替</button>
:
テーマ選択状態の保存
現在はテーマを切り替えても、ページをリロードすると初期状態に戻ってしまいます。後日、アクセスした場合に前に選択したテーマで表示されるように状態をローカルストレージに保存するようにします。
import { useState, useEffect } from 'react';
export default () => {
const [theme, setTheme] = useState('light');
useEffect(() => {
const localStorageTheme = localStorage.getItem('theme');
setTheme(localStorageTheme || 'light');
}, []);
const toggleTheme = () => {
if (theme === 'light') {
setTheme('dark');
localStorage.setItem('theme', 'dark');
} else {
setTheme('light');
localStorage.setItem('theme', 'light');
}
};
return [theme, toggleTheme];
};
ディスカッション
コメント一覧
まだ、コメントがありません