In the past few years, dark mode has become wildly popular. Results from User Experience (UX) surveys have shown that users have a higher preference for applications with the dark mode feature. According to the StackOverflow Developer Survey for 2020, 91.8% of software developers prefer dark mode enabled in their engineering tools. As of 2021, 83% of Reddit’s mobile app users used the platform with the dark display mode turned on.
While many web browsers and mobile apps operating systems have native support for dark/night themes, several web and mobile applications do not. The engineers behind the application must implement the support dark/light mode within the application’s styling. This tutorial will walk you through adding dark mode support to a React Application using Material UI styling.
This tutorial contains several hands-on steps to work with Material UI. To follow along, you will need to have the following:
Material User Interface (MUI) is the React adaptation of Google’s Material Design language, released in 2014. Since MUI’s launch, it has grown to become one of the most used design libraries in the React.js ecosystem, with over 4 million weekly downloads and over 85k stars on GitHub.
MUI provides developers with pre-built React components, templates, and design kits to reduce the development time of their user interfaces. A key benefit of MUI is its support for theming, which makes it possible for developers to customize the look and feel of the MUI components to suit their product designs.
The ThemeProvider component from MUI enables developers to specify consistent styling properties to apply across MUI components within the application. Examples of these styling properties include color, font, and shadow intensity. Underneath, the ThemeProvider component uses React’s Context API to pass your custom theme styles down to every MUI component within the application tree.
The ThemeProvider allows you to achieve consistent styling across components as you define style properties in an object for the theme and apply them to the MUI components via the useTheme() hook or makeStyles.
Within the previous Adding URL Previews to Chat tutorial, you built a demo chat application to learn how to generate URL previews through Weavy’s /embed endpoint. In this tutorial, you will add dark mode support to the demo URL preview application.
The GIF below shows a preview of the demo application with the dark/light theme modes.
If you prefer to dive right into the code, the completed project is available within the weavy-material-ui-dark-design folder of the Weavy Practical Demos repository.
Launch the terminal or command prompt application on your computer as you execute a series of commands to clone and set up the demo application.
Execute the Git command to clone the demo application from its GitHub repository or manually download the project files from its repository.
git clone https://github.com/weavy-labs/weavy-practical-demos.git
Execute the cd command to change the current directory into the weavy-react-chat-app
directory.
cd weavy-practical-demos && cd weavy-react-chat-app
Run the yarn command to install the application dependencies listed in the package.json
file:
yarn install
The cloned weavy-react-chat-app
project uses Material UI for CSS-In-JS styling and the React Context API to submit, fetch, and store message objects retrieved from the /messages endpoint within the Weavy API.
Open the project with the preferred code editor as you will begin to edit the project files.
Create a .env file at the root directory of the weavy-react-chat-app
project to securely store your Weavy resource credentials separately from the application code.
Replace the placeholders and add your Weavy Domain, User Token, and App ID values to the .env file using the format in the code block. The Adding URL Previews to Chat tutorial explains retrieving the Weavy Domain, User Token, and App ID values for a Weavy environment.
# weavy-react-chat-app/.env
VITE_WEAVY_DOMAIN=WEAVY_DOMAIN_URL
VITE_WEAVY_ACCESS_TOKEN=USER_TOKEN
VITE_APP_ID=CHAT_APP_ID
Execute the yarn dev command to start the Vite local server for the application.
Leave the local Vite server running in the terminal, as you will now focus on implementing the dark mode functionality in the application, starting from the Context state.
Within this section, you will add a string value to the application’s global state to enable you to switch the theme from multiple pages or components within the application, such as a Header component or Settings page. The ThemeProvider
component will manage its state internally.
Over the next code blocks, you will gradually modify the existing code within the app-context.jsx
file. You can open the app-context.jsx
file within the GitHub repository to view the file with the completed code.
Open the src/state/app-context.jsx
file and add the displayMode
value and toggleDisplayMode
method into the initialState object.
// src/state/app-context.jsx
const initialState = {
displayMode: "light",
toggleDisplayMode: () => {},
// existing state properties
}
As the default value, the displayMode
property in the initialState
object uses “light” for a light mode. In this tutorial's concluding parts, you will learn how to set the display mode to the user’s device OS preference.
Add the TOGGLE_DISPLAY_MODE
case below into the switch cases within the reducer function:
// src/state/app-context.jsx
case "TOGGLE_DISPLAY_MODE":
return {
...state,
displayMode: payload.displayMode,
};
Add the toggleDisplayMode
function into the AppProvider
wrapper component to implement a method of switching the displayMode
state:
// src/state/app-context.jsx
const toggleDisplayMode = () => {
actionDispatcher(
"TOGGLE_DISPLAY_MODE", {
displayMode: state.displayMode === "light" ? "dark" : "light"
});
}
Add the toggleDisplayMode
function within the value prop for the AppComponent.Provider
wrapper within the return statement as shown in the code below:
<AppContext.Provider value={ ...state, toggleDisplayMode,
submitChatMessage, actionDispatcher }>
{children}
</AppContext.Provider>
As mentioned earlier, you need to wrap your entire application tree with the ThemeProvider component to customize the theme of the MUI components within your interface. You do not need to use the ThemeProvider without custom themes, as MUI will use its default theme.
Let’s import and use the ThemeProvider component within the root App component.
Open the src/App.jsx
file to modify it with the code below to create the light and dark theme palettes for the MUI components across the application.
import React from "react";
import {
ThemeProvider
} from "@mui/material";
import {
grey
} from "@mui/material/colors"
import Home from "./pages/home";
import {
AppContext
} from "./state/app-context.jsx";
import {
createTheme
} from "@mui/material";
const darkPalette = {
primary: {
main: "#DDD",
},
text: {
primary: "rgba(255, 255, 255, 0.87)",
},
custom: {
main: '#FFFFFF1F',
},
surface: {
main: "#202020"
}
}
const lightPalette = {
primary: {
main: "#f3f4f6"
},
text: {
primary: "#000",
},
custom: {
main: grey[400],
},
surface: {
main: "#e5e7eb"
}
}
function App() {
const {
displayMode
} = React.useContext(AppContext)
const theme = React.useMemo(() =>
createTheme({
palette: {
mode: displayMode,
...(displayMode === "light" ?
lightPalette :
darkPalette)
}
}), [displayMode])
return (
);
}
export default App;
The App component above uses the ThemeProvider to wrap the entire application, and it receives a theme prop. The theme is created with the useMemo hook watching the displayMode global state value for changes. The useMemo
hook recreates the theme only when the displayMode
value has changed for performance.
The code separates the palette colors into the darkPalette variable for dark mode and the lightPalette variable for light mode. With the ternary operator, the palette colors will switch when the displayMode value changes to either light or dark.
Replace the code within the src/components/header using the dark palette in MUI components.jsx
file with the code below. The code will modify the Header component to contain an MUI Switch for toggling the display modes and two icons to indicate the current mode.
// src/components/header.jsx
import {
Box,
Switch,
Typography
} from "@mui/material";
import React from "react";
import {
AppContext
} from "../state/app-context.jsx";
import {
makeStyles,
useTheme
} from "@mui/styles";
import {
FiMoon,
FiSun
} from 'react-icons/fi'
const useStyles = makeStyles((theme) => ({
headerCtn: {
background: theme.palette.surface.main,
height: "75px",
display: "flex",
placeItems: "center",
alignItems: "center",
justifyContent: "center",
},
title: {
color: theme.palette.text.primary
},
}))
function Header() {
const {
displayMode,
toggleDisplayMode
} = React.useContext(AppContext)
const classes = useStyles()
const theme = useTheme()
return (
<Box
className={classes.headerCtn}
>
<Box
display={"flex"}
justifyContent="space-between"
px={"15px"}
width={"100%"}
>
<Box>
<Typography
variant={"h1"}
fontSize={"26px"}
className={classes.title}
>
Weavy Chat
</Typography>
</Box>
<Box display={"flex"} alignItems="center">
<Box display={"flex"}>
{
theme?.palette?.mode === "dark" ? (
<Box
display={"flex"}
alignItems={"center"}
fontSize={"32px"}
className={classes.title}
mr={"2px"}
>
<FiMoon/>
</Box>
) : (
<Box
display={"flex"}
alignItems={"center"}
fontSize={"32px"}
className={classes.title}
mr={"2px"}
>
<FiSun />
</Box>
)
}
<Switch
checked={displayMode === "dark"}
onChange={toggleDisplayMode}
inputProps=
/>
</Box>
</Box>
</Box>
</Box>
);
}
The onChange
handler of the Switch element within the Header component above executes the toggleDisplayMode
reducer action to change the displayMode state. The code also uses the mode property from the useTheme()
hook in a ternary operator to switch the header icon to indicate either a dark or light theme mode.
At this point, you have implemented the functionality to switch themes. Let’s modify the styles for the PostCard
and Home components to use the themes.
Open the src/components/PostCard.jsx
file and replace the useStyles variable with the code block below. The new code uses color values within the dark and light theme palettes. You can open the PostCard.jsx
file within the GitHub repository to view the file with the completed code.
// src/components/PostCard.jsx
// IMPORT STATEMENTS
const useStyles = makeStyles((theme) => ({
title: {
fontSize: "18px"
},
card: {
margin: "34px 8px 0 8px",
background: theme.palette.background.default,
borderRadius: "8px"
},
text: {
color: theme.palette.text.primary
},
previewContainer: {
background: theme.palette.surface.main,
},
image: {
width: "150px",
height: "100px",
objectFit: "cover"
}
}))
// POST CARD COMPONENT
export default PostCard;
Open the src/pages/home.jsx
file to update the Home styles as you did in the PostCard component, as you will gradually modify certain lines within the home.jsx
file. You can open the Home.jsx
file within the GitHub repository to view the file with the completed code.
Replace the useStyles
variable in the file with the one in the code block below. The new style object uses the palette colors to style the Home component and adapt to dark or light modes.
// src/pages/home.jsx
// EXISTING IMPORT STATEMENTS
const useStyles = makeStyles((theme) => ({
messagesContainer: {
height: "calc(100vh - 170px)",
overflow: "auto",
},
container: {
maxWidth: "650px",
margin: "auto",
},
messageWindow: {
background: theme.palette.custom.main,
padding: "0 12px"
},
messageInput: {
width: "100%",
padding: "12px",
background: theme.palette.surface.main,
color: theme.palette.text.primary,
outline: 0,
},
inputContainer: {
background: theme.palette.surface.main,
},
sendIcon: {
margin: "0 12px",
fontSize: "34px",
color: theme.palette.text.primary,
placeItems: "center",
"&:hover": {
cursor: "pointer"
}
},
formContainer: {
height: "60px",
width: "100%",
display: "flex",
border: `1px solid ${theme.palette.text.primary}`,
marginBottom: "30px"
}
}));
Replace the bgcolor
property within the sx prop of the top Box element to use MUI’s default background theme color as shown in the code below:
// src/pages/home.jsx
function Home() {
return (
<Box
height={"100vh"}
display={"flex"}
width={"100%"}
sx=
className={classes.page}
justifyContent={"center"}
>
// existing MUI elements
</Box>
);
}
export default Home;
At this point, you have modified the demo Weavy chat app to have dark mode support. Let’s proceed to view and test the dark mode support.
Open the running application through your web browser at http://localhost:5173 to view the changes made:
The demo application now starts in a light mode, and users can switch to a dark mode at the toggle of the switch at the Header. To improve the User Experience (UX), let’s detect when the user is accessing the application through a device on a dark theme and automatically switch to the dark theme.
Open the src/state/app-context.jsx
file in your preferred code editor.
Add the useMediaQuery
import to the existing imports, then the prefersDarkMode
variable and useEffect
hook into the AppProvider
component, right below the existing toggleDisplayMode()
function.
// src/state/app-context.jsx
// existing imports
import {
useMediaQuery
} from "@mui/material";
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
React.useEffect(toggleDisplayMode, [prefersDarkMode])
The prefersDarkMode variable uses the CSS prefers-color-scheme media query to know if the dark or light mode enabled. The useEffect hook will immediately execute the toggleDisplayMode function to activate the dark mode.
The tutorial taught you the importance of dark mode and its effect on your application’s UX. You also learned how to implement dark mode in a React application styled using MUI by following hands-on steps involving a demo application from Weavy.
Weavy provides a React UI Kit with several customizable components for developers building chat applications with the Weavy Chat API. The React UI Kit uses the Material Design V3 guidelines and fully supports themes, including dark and light modes.
To follow along with this tutorial, create a Weavy account for free:
Select what technology you want to ask about
Get answers faster with our tailored generative AI. Learn more about how it works
This is a custom LLM for answering your questions. Answers are based on the contents of the documentation and this feature is experimental.
To access live chat with our developer success team you need a Weavy account.