<div id="root">
</div>
:root {
  --text-color: #333;
  --bg-color: #fff;
  --header-bg-color: #eee;
}

@media (prefers-color-scheme: dark) {
  :root {
      --bg-color:  #333;
      --text-color:  #fff;
      --header-bg-color:  #222;
  }
}

body.dark-theme {
  --bg-color:  #333;
  --text-color:  #ddd;
  --header-bg-color:  #222;
}

body.light-theme {
  --bg-color: #fff;
  --text-color: #333;
  --header-bg-color: #eee;
}

body {
  background-color: var(--bg-color);
  color:  var(--text-color);
  transition: .5s;
	margin: 0;
}

.header {
	background-color: var(--header-bg-color);
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 1em;
	padding: 1em;
}

.container {
	height: 1000px;
	padding: 1em;
}
const { 
	useState,
	useRef,
	useCallback,
	useEffect,
	useLayoutEffect,
	forwardRef
} = React;

const useDarkModeButton = () => {
	const [isDarkMode, setIsDarkMode] = useState(false)
	const checkboxElement = useRef(null)

	const handleChange = useCallback(e => {
		const btn = e.target
		const body = document.body

		if(btn.checked === true) {
			body.classList.remove("light-theme")
			body.classList.add("dark-theme")

			Cookies.set('darkMode', 'on')

			//setIsDarkMode(true)
		}else{
			body.classList.remove("dark-theme")
			body.classList.add("light-theme")

			Cookies.remove('darkMode')
			//setIsDarkMode(false)
		}
	})

	useLayoutEffect(() => {
		const darkModeCookie = Cookies.get('darkMode')
		const body = document.body
		const checkbox = checkboxElement.current

		if(darkModeCookie){
			//body.classList.remove('light-theme');
	        body.classList.add('dark-theme');
	        checkbox.checked = true
			//setIsDarkMode(true)
		}else{
			//body.classList.remove('dark-theme');
	        //body.classList.add('light-theme');
	        //checkbox.checked = false
			//setIsDarkMode(false)
		}
	}, [])

	useEffect(() => {
		const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
		const darkModeOn = darkModeMediaQuery.matches
		const body = document.body
		const checkbox = checkboxElement.current

		darkModeMediaQuery.addListener((e) => {
		    const darkModeOn = e.matches;
		    if (darkModeOn) {
		        body.classList.remove('light-theme');
		        //body.classList.add('dark-theme');
				Cookies.set('darkMode', 'on')
		        checkbox.checked = true
		        //setIsDarkMode(true)
		    } else {
		        body.classList.remove('dark-theme');
		        //body.classList.add('light-theme');
				Cookies.remove('darkMode')
		        checkbox.checked = false
				//setIsDarkMode(false)
				
		    }
		});
	}, [])

	return [
		isDarkMode,
		checkboxElement,
		handleChange	
	]
}

const StyledIcon = styled.span`
	color: ${({ color }) => color};
	font-size: ${({ size }) => size};
`

const Icon = ({ name, color, size }) => (
	<StyledIcon className="icon" color={color} size={size}>
		<i class={`fas fa-${name}`}></i>
	</StyledIcon>
)

const StyledToggleSwitchButton = styled.div`
	& input {
		display: none;
		&:checked + label {
			background-color: ${({onColor}) => onColor};
			&::before {
				//order: 2;
				left: 2em;
			}
		}
	}

	& label {
		background-color: ${({offColor}) => offColor};
		border-radius: 2em;
		//border: 2px solid var(--text-color);
		cursor: pointer;
		//display: block;
		display: flex;
		align-items: center;
		font-size: ${({ size }) => size};2.4em;
		justify-content: space-around;
		height: 2em;
		position: relative;
		transition: .2s;// 本体の色
		width: 3.75em;

		&::before {
			background-color: #fff;
			border-radius: 100%;
			content: '';
			display: inline-block;
			height: 1.5em;
			position: absolute;
			left: 0.25em;
			transition: .2s ease-out;
			width: 1.5em;
			z-index: 2;
		}

		&::after {
			background-color: red;
			border-radius: 100%;
			content: '';
			display: inline-block;
			height: 1.5em;
			position: absolute;
			right: .25em;
			visibility: hidden;
			width: 1.5em;
			z-index: 2;
		}
		
		& .icons {
			border-radius: 2em;
			display: flex;
			align-items: center;
			justify-content: space-around;
			position: absolute;
			top: 0;
			left: 0;
			bottom: 0;
			right: 0;
			z-index: 1;
		}
	}
`

const ToggleSwitchButton = forwardRef(({ className, handleChange, offColor, onColor, size }, ref) => (
	<StyledToggleSwitchButton
		className={className}
		offColor={offColor}
		onColor={onColor}
		size={size}
	>
		<input id="btn-mode" type="checkbox" onChange={handleChange} ref={ref} />
		<label htmlFor="btn-mode">	
		</label>
	</StyledToggleSwitchButton>
))

const DarkModeSwitchButton = forwardRef(({ className, handleChange }, ref) => (
	<StyledDarkModeSwitchButton className={className}>
		<input id="btn-mode" type="checkbox" onChange={handleChange} ref={ref} />
		<label htmlFor="btn-mode">
			<div className="icons">
				<Icon className="icon icon-light" name="sun" />
				<Icon className="icon icon-dark" name="moon" />
			</div>
		</label>
	</StyledDarkModeSwitchButton>
))

const App = () => {
	 const [
  		isDarkMode,
  		checkboxElement,
		handleChangeDarkMode
	] = useDarkModeButton()

	return (
		<div>
			<header className="header">
				<div>LOGO</div>
				<ToggleSwitchButton
					className="toggle-switch-button"
					handleChange={handleChangeDarkMode}
					ref={checkboxElement}
					offColor="#ccc"
					onColor="#383896"
					size="1em"
				/>
			</header>
			<div className="container">
				<div>example</div>
			</div>
		</div>
	);
};

ReactDOM.render(<App />, document.getElementById("root"));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/styled-components/4.3.1/styled-components.min.js
  4. https://use.fontawesome.com/releases/v5.1.0/js/all.js
  5. https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.2.1/js.cookie.min.js