I'd like to implement switching between dark/light theme dynamically with Ant design v4.
It's possible to customize the theme with other CSS/LESS imports as it's written here: https://ant.design/docs/react/customize-theme#Use-dark-theme
But I'm not sure how to switch between those themes dynamically from the code. I have a variable in my React app (darkMode) which indicates if the dark theme is currently used. I have to provide correct CSS files when this variable is changed. But I can't import CSS dynamically only when some condition is fulfilled, because it's not way how the imports work.
I tried to do something messy with require like in the following code, but it's a very very bad approach and it's still not working properly (because CSS is injected but probably not withdrawn.
):
const Layout = () => {
...
useEffect(() => {
if (darkMode === true) {
require("./App.dark.css")
} else {
require("./App.css")
}
}, [darkMode])
return (
<Home />
)
}
It should be possible to switch themes somehow because it's already implemented in Ant design docs (https://ant.design/components/button/):
Do you have any idea how to do it?
Thanks!
Conditional require won't block using previously required module. So, whenever your condition matches the require will available in your app. So, your both required module will be used. Instead of requiring them, insert stylesheet and remove to toggle between them:
const head = document.head
const dark = document.createElement('link')
const light = document.createElement('link')
dark.rel = 'stylesheet'
light.rel = 'stylesheet'
dark.href = 'antd.dark.css'
light.href = 'antd.light.css'
useEffect(() => {
const timer = setTimeout(() => {
if (darkMode) {
if (head.contains(light)) {
head.removeChild(light)
}
head.appendChild(dark)
} else {
if (head.contains(dark)) {
head.removeChild(dark)
}
head.appendChild(light)
}
}, 500)
return () => clearTimeout(timer)
}, [darkMode])
This is what I am using for now -
PS -
make a folder called "themes" - it would have 6 files -> dark-theme.css, dark-theme.jsx, light-theme.css, light-theme.jsx, use-theme.js, theme-provider.jsx. Each of them is described below.
dark-theme.css
import "~antd/dist/antd.dark.css";
dark-theme.jsx
import "./dark-theme.css";
const DarkTheme = () => <></>;
export default DarkTheme;
light-theme.css
@import "~antd/dist/antd.css";
light-theme.jsx
import "./light-theme.css";
const LightTheme = () => <></>;
export default LightTheme;
use-theme.js A custom hook that different components can use -
import { useEffect, useState } from "react";
const DARK_MODE = "dark-mode";
const getDarkMode = () => JSON.parse(localStorage.getItem(DARK_MODE)) || false;
export const useTheme = () => {
const [darkMode, setDarkMode] = useState(getDarkMode);
useEffect(() => {
const initialValue = getDarkMode();
if (initialValue !== darkMode) {
localStorage.setItem(DARK_MODE, darkMode);
window.location.reload();
}
}, [darkMode]);
return [darkMode, setDarkMode];
};
theme-provider.jsx
import { lazy, Suspense } from "react";
import { useTheme } from "./use-theme";
const DarkTheme = lazy(() => import("./dark-theme"));
const LightTheme = lazy(() => import("./light-theme"));
export const ThemeProvider = ({ children }) => {
const [darkMode] = useTheme();
return (
<>
<Suspense fallback={<span />}>
{darkMode ? <DarkTheme /> : <LightTheme />}
</Suspense>
{children}
</>
);
};
change index.js to -
ReactDOM.render(
<React.StrictMode>
<ThemeProvider>
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("root")
);
now, in my navbar suppose I have a switch to toggle the theme. This is what it would look like -
const [darkMode, setDarkMode] = useTheme();
<Switch checked={darkMode} onChange={setDarkMode} />
you must create 2 components
the first one :
import './App.dark.css'
const DarkApp =() =>{
//the app container
}
and the second :
import './App.light.css'
const LightApp =() =>{
//the app container
}
and create HOC to handle darkMode like this :
const AppLayout = () =>{
const [isDark , setIsDark] = useState(false);
return (
<>
{
isDark ?
<DarkApp /> :
<LightApp />
}
</>
)
}
In Ant's example one suggestion is to import your "dark mode" CSS or LESS file into your main style sheet.
// inside App.css
@import '~antd/dist/antd.dark.css';
Instead of trying to toggle stylesheets, the "dark" styles are combined with base styles in one stylesheet. There are different ways to accomplish this, but the common pattern will be:
dark-mode selector of some sort in your CSSHere is a working example:
https://codesandbox.io/s/compassionate-elbakyan-f7tun?file=/src/App.js
In this example, toggling the state of darkMode will add or remove a dark-mode className to the top level container.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [darkMode, setDarkMode] = useState(false);
return (
<div className={`App ${darkMode && "dark-mode"}`}>
<label>
<input
type="checkbox"
checked={darkMode}
onChange={() => setDarkMode((darkMode) => !darkMode)}
/>
Dark Mode?
</label>
<h1>Hello CodeSandbox</h1>
</div>
);
}
If darkMode is true, and the dark-mode className is present, those styles will be used:
h1 {
padding: 0.5rem;
border: 3px dotted red;
}
.dark-mode {
background: black;
color: white;
}
.dark-mode h1 {
border-color: aqua;
}
Using less compiler in runtime:
https://medium.com/@mzohaib.qc/ant-design-dynamic-runtime-theme-1f9a1a030ba0
Import less code into wrapper
https://github.com/less/less.js/issues/3232
.any-scope {
@import url('~antd/dist/antd.dark.less');
}