Implementing Dark mode for React app with MUI
In modern web applications, offering users a choice between light and dark themes is common. In this article, I explain how to use MUI combined with React + Typescript to implement a theme toggle feature for your React application. You can use this project as a starter project, too.
Prerequisites
You should have a basic knowledge of React and how React Context works.
Let's build it together!
First, start your React project. I'm using Vite for installing React with Typescript.
npm create vite@latest YOUR_APP_NAME --template react-ts
Then, install MUI for theming and UI components. Further, Material Icons since we use a couple of icons for this app.
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
Create your ThemeContextProvider.tsx
file in the src
folder.
Here, we're creating and exporting a new context for other components to access theme-related functionalities. We initialize our theme mode state to "light" by default. This state determines whether the user sees the light or dark theme. With the help of createTheme
we define our MUI theme, setting the palette's mode based on our mode
state.
import {
StyledEngineProvider,
ThemeProvider,
createTheme,
} from "@mui/material/styles";
import { ReactNode, createContext, useMemo, useState } from "react";
type ThemeContextType = {
switchColorMode: () => void;
};
type ThemeProviderProps = {
children: ReactNode;
};
export const ThemeContext = createContext<ThemeContextType>({
switchColorMode: () => {},
});
export function ThemeContextProvider({ children }: ThemeProviderProps) {
const [mode, setMode] = useState<"light" | "dark">("light");
const switchColorMode = () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
};
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);
return (
<StyledEngineProvider injectFirst>
<ThemeContext.Provider value={{ switchColorMode }}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</ThemeContext.Provider>
</StyledEngineProvider>
);
}
In your App.tsx
file, define your top bar (AppBar
) with an icon button on the right that switches between light and dark modes when clicked. We use the useTheme
hook to get the current theme properties and the useContext
hook to extract the switchColorMode
function from our ThemeContext
.
import { DarkModeOutlined, LightModeOutlined } from "@mui/icons-material";
import {
AppBar,
Box,
Container,
IconButton,
Toolbar,
Tooltip,
useTheme,
} from "@mui/material";
import { useContext, useMemo } from "react";
import { ThemeContext } from "./theme";
function App() {
const theme = useTheme();
const { switchColorMode } = useContext(ThemeContext);
const activateName = useMemo(
() => (theme.palette.mode === "dark" ? "Light" : "Dark"),
[theme]
);
return (
<>
<Container
maxWidth={false}
sx={{
padding: "0px !important",
}}
>
<AppBar
position="static"
sx={{
padding: "0px !important",
bgcolor: theme.palette.background.default,
}}
>
<Toolbar>
<Box flex={1}></Box>
<Box sx={{ flexGrow: 0 }}>
<Tooltip title={`Activate ${activateName} Mode`}>
<IconButton
onClick={switchColorMode}
sx={{
p: 1,
border: `1px ${theme.palette.text.disabled} solid`,
}}
size="large"
color="inherit"
>
{theme.palette.mode === "dark" ? (
<LightModeOutlined />
) : (
<DarkModeOutlined color="action" />
)}
</IconButton>
</Tooltip>
</Box>
</Toolbar>
</AppBar>
</Container>
</>
);
}
export default App;
Since this is a starter project, I use only the top bar to show the dark and light mode themes. Later, you can add more components and play with the dark and light modes.
Remember to update your main.tsx
file. Here, I import ThemeContextProvider
and wrap the App
component.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { ThemeContextProvider } from "./theme";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ThemeContextProvider>
<App />
</ThemeContextProvider>
</React.StrictMode>
);
Below is the outcome of the above code. The Tooltip
component adds some user-friendly context, showing a message when a user hovers over the button, informing them of its function.
Here's the GitHub repository for the complete code. Give it a star ⭐️ if you find it useful.
I'll see you guys in my next post. Until then, happy coding! 👨💻