This post was created using version 3.x.x of refine. Although we plan to update it with the latest version of refine as soon as possible, you can still benefit from the post in the meantime.
You should know that refine version 4.x.x is backward compatible with version 3.x.x, so there is no need to worry. If you want to see the differences between the two versions, check out the migration guide.
In this article, we will provide an example on how to implement darkmode with refine. In order to switch between light and dark mode, we need 2 different styles and the possibility to switch between one and the other without restarting the application. Since the Less stylesheets with React doesn't allow variables to be modified without compilation and therefore a restart of the application. To solve this, we are going to use gulp that will compile the Less files into swappable CSS, directly accessible to the running application.
The solution was presented in this blog and lightly adapted to Refine.
Initial setupโ
For this article, we started from a basic Refine app with Ant Design:
npm create refine-app@latest tutorial -- -p refine-react -b v3
Select the following options to complete the CLI wizard:
Cloned remote source successfully.
โ What will be the name of your app ยท tutorial
โ Package manager: ยท Npm
โ Do you want to use a UI Framework?: ยท Ant Design
โ Do you want a customized theme?: ยท Yes (Custom Variables)
โ Router Provider: ยท React Router v6
โ Data Provider: ยท REST API
โ Auth Provider: ยท None
โ Do you want to add example pages? ยท Yes (Recommended)
โ Do you want a customized layout? ยท Yes
โ i18n - Internationalization: ยท No
From there, install the following packages:
npm install -s gulp gulp-less gulp-postcss gulp-debug gulp-csso autoprefixer less-plugin-npm-import
npm install -s react-redux react-css-theme-switcher
Create the Less and then CSS filesโ
Copy the antd.less
fileโ
Make a copy of the src/styles/antd.less
file into antd.light-theme.less
and add the following lines inside (because this setting will be removed from the Header/index.tsx file later on):
// Header
@layout-header-background:#fff;
Create a Less file for the dark modeโ
Create the following file src/styles/antd.dark-theme.less
with this content:
// Run 'npx gulp less' after modifying this file
@import '~antd/lib/style/color/colorPalette.less';
@import '~antd/dist/antd.less';
@import '~antd/lib/style/themes/dark.less';
@primary-color: rgba(255, 255, 255, 0.75);
@border-radius-base: 4px;
@icon-color: rgba(255, 255, 255, 0.75);
@component-background: #303030;
@body-background: #303030;
@popover-background: #303030;
@border-color-base: #6f6c6c;
@border-color-split: #424242;
@table-header-sort-active-bg: #424242;
@card-skeleton-bg: #424242;
@skeleton-color: #424242;
@table-header-sort-active-bg: #424242;
@layout-header-background:#424242;
Compile the CSS files with gulp
โ
Create the following gulpfile.js
in the root of the repo:
const gulp = require('gulp')
const gulpless = require('gulp-less')
const postcss = require('gulp-postcss')
const debug = require('gulp-debug')
var csso = require('gulp-csso')
const autoprefixer = require('autoprefixer')
const NpmImportPlugin = require('less-plugin-npm-import')
gulp.task('less', function () {
const plugins = [autoprefixer()]
return gulp
.src('src/styles/*-theme.less')
.pipe(debug({title: 'Less files:'}))
.pipe(
gulpless({
javascriptEnabled: true,
plugins: [new NpmImportPlugin({prefix: '~'})],
}),
)
.pipe(postcss(plugins))
.pipe(
csso({
debug: true,
}),
)
.pipe(gulp.dest('./public'))
})
exports.sync = gulp.series('less');
And run npx gulp less
โถnpx gulp less
[22:36:28] Using gulpfile ./gulpfile.js
[22:36:28] Starting 'less'...
[22:36:28] Less files: src/styles/antd.dark-theme.less
[22:36:28] Less files: src/styles/antd.light-theme.less
[22:36:28] Less files: 2 items
## parsing done in 165 ms
Compress block #1
[0.028s] init
[0.038s] clean
[0.068s] replace
[0.076s] prepare
[0.011s] mergeAtrule
[0.052s] initialMergeRuleset
[0.011s] disjoinRuleset
[0.068s] restructShorthand
[0.076s] restructBlock
[0.015s] mergeRuleset
[0.075s] restructRuleset
## compress done in 522 ms
## generate done in 44 ms
## parsing done in 114 ms
Compress block #1
[0.007s] init
[0.020s] clean
[0.057s] replace
[0.067s] prepare
[0.006s] mergeAtrule
[0.068s] initialMergeRuleset
[0.012s] disjoinRuleset
[0.045s] restructShorthand
[0.033s] restructBlock
[0.010s] mergeRuleset
[0.061s] restructRuleset
## compress done in 389 ms
## generate done in 15 ms
[22:36:33] Finished 'less' after 5 s
this command must be repeated each time the Less files are modified and the application restarted to see the changes)
You should now have 2 CSS files inside the public folder: antd.dark-theme.css
and antd.light-theme.css
Adapt the Refine application to be able to switch between the 2 stylesโ
App.tsx fileโ
// highlight-start // highlight-end In App.tsx, adapt the file so it looks like thisย :
import { Refine, } from '@refinedev/core';
import { notificationProvider } from '@refinedev/antd';
import routerProvider from "@refinedev/react-router-v6";
import "styles/antd.less";
import dataProvider from "@refinedev/simple-rest";
import { PostList, PostCreate, PostEdit, PostShow } from "pages/posts";
import { Title, Header, Sider, Footer, Layout, OffLayoutArea } from "components/layout"
import { ThemeSwitcherProvider } from "react-css-theme-switcher";
function App() {
const currThemes = {
dark: `${process.env.PUBLIC_URL}/antd.dark-theme.css`,
light: `${process.env.PUBLIC_URL}/antd.light-theme.css`,
};
return (
<ThemeSwitcherProvider themeMap={currThemes} defaultTheme="light">
<Refine routerProvider={routerProvider} notificationProvider={notificationProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
resources={[
{
name: "posts",
list: PostList,
create: PostCreate,
edit: PostEdit,
show: PostShow,
},
]}
Title={Title}
Header={Header}
Sider={Sider}
Footer={Footer}
Layout={Layout}
OffLayoutArea={OffLayoutArea} />
</ThemeSwitcherProvider>
);
};
export default App;
Headerโ
Add a theme switcher in the Header (src/components/layout/header/index.tsx
) with the added lines so it looks like this (the Header has been simplified for the sake of clarity and the switch can be installed somewhere else in the application obviously):
import { useState } from "react";
import { useThemeSwitcher } from "react-css-theme-switcher";
import {
AntdLayout,
Switch,
} from "@refinedev/antd";
export const Header: React.FC = () => {
const [isDarkMode, setIsDarkMode] = useState<boolean>();
const { switcher, themes } = useThemeSwitcher();
function toggleTheme(isChecked: boolean) { // added
setIsDarkMode(isChecked);
switcher({ theme: isChecked ? themes.dark : themes.light });
};
return (
<AntdLayout.Header
style={{
display: "flex",
justifyContent: "flex-end",
alignItems: "center",
padding: "0px 24px",
height: "64px",
//backgroundColor: "#FFF", // commented out, otherwise the header remains white in dark mode
}}
>
<div className="main fade-in"> // added
<Switch
checkedChildren="๐"
unCheckedChildren="๐"
checked={isDarkMode}
onChange={toggleTheme}
/>
</div>
</AntdLayout.Header>
);
};
You should now have a light/dark mode switcher in the header: