Recommended resources
The freely available resources this lesson will direct you to.
Hello! In the previous lesson, you moved the user directory into a page slice and left application-wide setup in app. That separation now has a practical use: the directory should not have to choose its own colors, fonts, and control styles one component at a time.
In this lesson, you’ll configure a Material UI theme for those shared design decisions, install it above the page, and verify that component defaults apply while individual components can still opt out. Allow about 40 minutes, including time to update and check your project.
One theme, many components
A theme is a configuration object that Material UI components can read from a ThemeProvider above them in the React tree. Its palette assigns meanings to colors, typography defines a consistent type scale, and components supplies defaults for particular Material UI components. That is a better fit for app-wide decisions than repeating a hex value or button variant throughout the directory.
Watch Bruno Antunes’s React Material-UI Themes: Customize Material Components for your Project for a visual demonstration. The video is useful for seeing changes propagate; use the current code in this lesson for your TypeScript implementation.
React Material-UI Themes: Customize Material Components for your Project
In Bruno Antunes’s React Material-UI Themes video, watch how palette and typography changes affect multiple components, then how component styles and default props are configured.
Watch palette and type for primary and secondary colors, the font family, and typography variants. Then watch component styles and default props. Focus on the distinction between changing a component’s styling and choosing props it receives by default; you do not need to adopt every override shown.
The Material UI theming guide clarifies why the provider’s position matters.
Read Material UI’s introduction and “Theme provider” section to connect the configuration object to the components that consume it.
From the introductory paragraph beginning “The theme specifies the color of the components,” read through Theme provider. Notice that a component must be below the provider to receive your custom theme.
Put the provider around the application, not inside UserDirectoryPage. A page should consume the design system without owning its setup.
Define palette and typography in app
First, install Material UI and its required styling packages in your Vite project:
npm install @mui/material @emotion/react @emotion/styled
Before choosing colors, read the palette guide’s explanation of its named colors and tokens. In particular, primary means a principal interface color; it does not mean “every color used on the page.”
Read Material UI’s palette guide to understand what a color’s main value supplies and what Material UI can calculate when you omit other shades.
In “Color tokens,” start at “Palette colors are represented by four tokens” and read through Default colors; focus on the token meanings and the named palette roles rather than memorizing the sample values. Then, under “Customization” → “Providing the colors directly,” read the introductory paragraph. It explains why specifying main is enough for this first theme.
Create src/app/theme.ts:
import { createTheme } from '@mui/material/styles'
export const appTheme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#155E75',
},
secondary: {
main: '#5B4B8A',
},
background: {
default: '#F5F7FA',
paper: '#FFFFFF',
},
},
typography: {
fontFamily:
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
h4: {
fontSize: '1.75rem',
fontWeight: 700,
},
h6: {
fontSize: '1.125rem',
fontWeight: 600,
},
},
components: {
MuiButton: {
defaultProps: {
variant: 'outlined',
},
styleOverrides: {
root: {
textTransform: 'none',
fontWeight: 600,
},
},
},
MuiTextField: {
defaultProps: {
size: 'small',
variant: 'outlined',
},
},
},
})
These values are an example for a restrained back-office interface, not colors you must keep. The important decision is where they live. Changing primary.main here changes components that use the primary color; changing h4 changes Material UI typography using that variant. Other palette colors and typography variants retain their defaults unless you configure them.
The font stack uses fonts commonly available on the device, so this setup does not require a separate font download. Later, if you select a particular font, you will need to load it as well as name it in fontFamily.
Notice the difference between the two MuiButton settings. defaultProps.variant chooses outlined when a button does not specify a variant. styleOverrides.root adjusts styling common to buttons. Neither removes the ability to write <Button variant="contained"> for an action that should look different.
Provide the theme at the application boundary
In src/main.tsx, keep your existing root-rendering setup and stylesheet imports, but wrap App with ThemeProvider. Render CssBaseline once inside it:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider } from '@mui/material/styles'
import App from './app/App'
import { appTheme } from './app/theme'
// Keep any existing global stylesheet imports here.
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ThemeProvider theme={appTheme}>
<CssBaseline />
<App />
</ThemeProvider>
</StrictMode>,
)
ThemeProvider makes the theme available to descendants, including the directory page. CssBaseline applies foundational document styles, such as body background and typography, so the page around Material UI components does not retain unrelated browser defaults.
Check your Vite starter styles before judging the result. Rules such as body { display: flex; place-items: center; }, a centered #root with a fixed maximum width, or a hard-coded dark background can work against a full-page back-office design. Remove or revise conflicting starter rules, but keep styles that your existing directory behavior actually needs. The theme does not automatically override every CSS rule you have written.
This placement preserves the boundary from the last lesson: app owns the theme and provider; the page does not import appTheme. The page can simply render Material UI components beneath the provider.
Check the defaults without redesigning the directory
Use a small part of the existing directory to verify the setup. For example, you can replace its current page title with a Material UI heading:
import Typography from '@mui/material/Typography'
// Inside the page's existing JSX:
<Typography variant="h4" component="h1">
User directory
</Typography>
Here, variant="h4" selects the themed visual style, while component="h1" keeps the page title an HTML level-one heading. The two choices serve different purposes: visual scale and document structure.
If the page already has a clear-search action, a Material UI button can demonstrate both the default and an explicit override:
import Button from '@mui/material/Button'
// Adapt the state setter name to your existing page.
<Button onClick={() => setSearch('')}>Clear search</Button>
<Button variant="text" onClick={() => setSearch('')}>
Clear search
</Button>
You only need one clear-search button in the finished page. Compare the two forms while checking the theme, then keep the one that suits the action. The first gets outlined from the theme; the second explicitly requests text. Do not add a second search state or change the directory’s behavior just to demonstrate styling.
Run your checks:
npm run typecheck
npm run build
npm run dev
In the browser, confirm that the heading uses the configured type scale and that a Material UI button picks up the configured default. If a color or font appears unchanged, check whether the element is a Material UI component, whether it sits beneath ThemeProvider, and whether an existing CSS rule is taking precedence. Also confirm that search, selection, and the Escape shortcut still work: this lesson changes presentation, not those interactions.
What to carry forward
A centralized theme gives palette colors semantic roles, typography variants a consistent scale, and components sensible default props and styles. createTheme defines those choices; ThemeProvider makes them available throughout the app; individual components can still override a default when their purpose calls for it.
Next, you’ll use this foundation to build a responsive back-office shell. Its header, navigation, and main content region can share the theme without each page inventing its own visual rules.