TutorialsChat

Add dark mode to your React app using Material UI

Jul 15, 2023

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. 

 

Prerequisites  

This tutorial contains several hands-on steps to work with Material UI. To follow along, you will need to have the following: 

  • A basic understanding of JavaScript and the React.js framework to follow along with building the demo application. 
  • A Weavy account to access the Weavy Chat API.


What Is Material UI?

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. 

MUI Support For Theming

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. 

Demo: Adding dark mode support to the URL Preview application

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. 

Setting Up The Demo Application 

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.

Adding dark mode to the global 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> 


Adding the ThemeProvider component

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. 


Using the dark palette in MUI components

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:

 

Adapting to a user’s theme preference 

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:

Get started

 
Weavy

Share this post