Skip to main content

React Material UI

Introduction

Material UI is one of the most popular UI libraries for React, implementing Google's Material Design guidelines. It provides a comprehensive set of pre-built, customizable React components that help developers build beautiful and consistent user interfaces quickly.

In this guide, we'll explore how to integrate Material UI into a React application, understand its core concepts, and learn how to use its components to create modern, responsive interfaces without having to write extensive CSS from scratch.

Why Use Material UI?

Material UI offers several advantages for React developers:

  • Ready-to-use components: Buttons, forms, navigation, and more that follow design best practices
  • Consistent styling: Components that work well together with a cohesive look and feel
  • Responsive design: Built-in mobile-first approach
  • Customization: Ability to override default styles and create custom themes
  • Accessibility: Built with accessibility in mind
  • Active community: Regular updates and extensive documentation

Getting Started with Material UI

Installation

First, you'll need to install Material UI in your React project:

bash
npm install @mui/material @emotion/react @emotion/styled
# or
yarn add @mui/material @emotion/react @emotion/styled

For icons (optional but recommended):

bash
npm install @mui/icons-material
# or
yarn add @mui/icons-material

Basic Setup

Once installed, you can start using Material UI components in your React application. Let's create a simple example:

jsx
import React from 'react';
import Button from '@mui/material/Button';

function App() {
return (
<div>
<Button variant="contained" color="primary">
Hello Material UI
</Button>
</div>
);
}

export default App;

This will render a primary-colored Material UI button with contained styling:

Core Concepts

The ThemeProvider

Material UI uses React's context API to provide a theme to all components. Wrap your application with ThemeProvider to ensure consistent styling:

jsx
import React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import App from './App';

// Create a theme
const theme = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
});

function ThemedApp() {
return (
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
);
}

export default ThemedApp;

Styling Components

Material UI offers multiple approaches to styling:

  1. Predefined Props: Using the sx prop for one-off styling
  2. Styled API: Using the styled components approach
  3. Theme customization: Modifying the global theme

Using the sx Prop

The sx prop is a shortcut for defining custom styles that has access to the theme:

jsx
import Box from '@mui/material/Box';

function StyledComponent() {
return (
<Box
sx={{
width: 300,
height: 100,
backgroundColor: 'primary.main',
'&:hover': {
backgroundColor: 'primary.dark',
opacity: [0.9, 0.8, 0.7],
},
}}
>
This Box has custom styling
</Box>
);
}

Using the Styled API

For more complex components, you can use the styled API:

jsx
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';

const CustomButton = styled(Button)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
padding: theme.spacing(2),
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
}));

function StyledButtonExample() {
return <CustomButton>Custom Styled Button</CustomButton>;
}

Essential Material UI Components

Let's explore some of the most commonly used components in Material UI.

Buttons and Variants

Material UI offers different button variants:

jsx
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

function ButtonVariants() {
return (
<Stack spacing={2} direction="row">
<Button variant="text">Text</Button>
<Button variant="contained">Contained</Button>
<Button variant="outlined">Outlined</Button>
</Stack>
);
}

Input Fields and Forms

Creating forms with Material UI is straightforward:

jsx
import React, { useState } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';

function SimpleForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
});

const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};

const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
};

return (
<Box component="form" onSubmit={handleSubmit} sx={{ '& > :not(style)': { m: 1 } }}>
<TextField
name="name"
label="Full Name"
variant="outlined"
value={formData.name}
onChange={handleChange}
fullWidth
/>
<TextField
name="email"
label="Email"
variant="outlined"
type="email"
value={formData.email}
onChange={handleChange}
fullWidth
/>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</Box>
);
}

Cards for Content Display

Cards are a great way to display content:

jsx
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';

function ProductCard() {
return (
<Card sx={{ maxWidth: 345 }}>
<CardMedia
component="img"
height="140"
image="/product-image.jpg"
alt="product"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
Product Title
</Typography>
<Typography variant="body2" color="text.secondary">
This is a description of the product. It explains the features
and benefits of purchasing this item.
</Typography>
</CardContent>
<CardActions>
<Button size="small">Share</Button>
<Button size="small">Learn More</Button>
</CardActions>
</Card>
);
}

Material UI provides components for creating navigation interfaces:

jsx
import React, { useState } from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import Drawer from '@mui/material/Drawer';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';

function NavigationExample() {
const [drawerOpen, setDrawerOpen] = useState(false);

const toggleDrawer = (open) => (event) => {
if (
event.type === 'keydown' &&
(event.key === 'Tab' || event.key === 'Shift')
) {
return;
}
setDrawerOpen(open);
};

return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="menu"
sx={{ mr: 2 }}
onClick={toggleDrawer(true)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
My Application
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>

<Drawer
anchor="left"
open={drawerOpen}
onClose={toggleDrawer(false)}
>
<Box
sx={{ width: 250 }}
role="presentation"
onClick={toggleDrawer(false)}
onKeyDown={toggleDrawer(false)}
>
<List>
{['Home', 'Products', 'About', 'Contact'].map((text) => (
<ListItem button key={text}>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Box>
</Drawer>
</Box>
);
}

Building a Responsive Layout

Material UI provides Grid and Box components for creating responsive layouts:

jsx
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';

// Create styled component
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));

function ResponsiveGridLayout() {
return (
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4}>
<Item>xs=12 sm=6 md=4</Item>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Item>xs=12 sm=6 md=4</Item>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Item>xs=12 sm=6 md=4</Item>
</Grid>
</Grid>
</Box>
);
}

In this example, the grid items will be:

  • One column on extra small screens (xs)
  • Two columns on small screens (sm)
  • Three columns on medium and larger screens (md)

Creating a Custom Theme

You can customize Material UI's default theme to match your brand:

jsx
import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';

// Create a custom theme
const theme = createTheme({
palette: {
primary: {
main: '#3f51b5',
},
secondary: {
main: '#f50057',
},
background: {
default: '#f5f5f5',
},
},
typography: {
fontFamily: [
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
].join(','),
h1: {
fontSize: '2.5rem',
fontWeight: 500,
},
button: {
textTransform: 'none',
},
},
shape: {
borderRadius: 8,
},
});

function ThemedApp({ children }) {
return (
<ThemeProvider theme={theme}>
<CssBaseline /> {/* Provides baseline CSS normalization */}
{children}
</ThemeProvider>
);
}

Real-world Example: Product Listing Page

Let's build a real-world example of a product listing page using Material UI:

jsx
import React, { useState } from 'react';
import {
Container, Grid, Card, CardMedia, CardContent, CardActions,
Typography, Button, AppBar, Toolbar, TextField, InputAdornment,
Box, Rating, Chip
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';

// Sample product data
const products = [
{
id: 1,
name: 'Wireless Headphones',
price: 129.99,
rating: 4.5,
image: '/headphones.jpg',
category: 'Electronics'
},
{
id: 2,
name: 'Running Shoes',
price: 89.99,
rating: 4.2,
image: '/shoes.jpg',
category: 'Sports'
},
{
id: 3,
name: 'Coffee Maker',
price: 59.99,
rating: 3.8,
image: '/coffee-maker.jpg',
category: 'Home'
},
// More products...
];

function ProductListingPage() {
const [searchTerm, setSearchTerm] = useState('');

const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);

return (
<Box>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
MyShop
</Typography>
<Button color="inherit" startIcon={<ShoppingCartIcon />}>
Cart (0)
</Button>
</Toolbar>
</AppBar>

<Container sx={{ mt: 4 }}>
<TextField
fullWidth
margin="normal"
placeholder="Search products..."
variant="outlined"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>

<Typography variant="h4" component="h1" sx={{ my: 4 }}>
Products {searchTerm && `matching "${searchTerm}"`}
</Typography>

<Grid container spacing={4}>
{filteredProducts.map((product) => (
<Grid item key={product.id} xs={12} sm={6} md={4}>
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<CardMedia
component="img"
height="200"
image={product.image}
alt={product.name}
/>
<CardContent sx={{ flexGrow: 1 }}>
<Typography gutterBottom variant="h5" component="h2">
{product.name}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6" color="primary">
${product.price.toFixed(2)}
</Typography>
<Chip label={product.category} size="small" />
</Box>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Rating value={product.rating} precision={0.5} readOnly />
<Typography variant="body2" sx={{ ml: 1 }}>
{product.rating}
</Typography>
</Box>
</CardContent>
<CardActions>
<Button
size="small"
variant="contained"
startIcon={<ShoppingCartIcon />}
sx={{ marginLeft: 'auto' }}
>
Add to Cart
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</Container>
</Box>
);
}

This example demonstrates how Material UI components can be combined to create a complete interface that is both functional and responsive.

Advanced Topics

Server-side Rendering

Material UI supports server-side rendering. Here's a basic setup for Next.js:

jsx
// pages/_document.js
import * as React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@mui/styles';

export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

// Collect styles for SSR
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;

ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});

const initialProps = await Document.getInitialProps(ctx);

return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};

Performance Optimization

To optimize performance with Material UI, consider the following:

  1. Import components directly rather than from the main package:
jsx
// Better for performance
import Button from '@mui/material/Button';

// Less efficient
import { Button } from '@mui/material';
  1. Use dynamic imports for large components not needed immediately:
jsx
import React, { Suspense, lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}

Common Pitfalls and Solutions

Too Many Re-renders

jsx
// Problem: Inline object creation causes unnecessary re-renders
<Button sx={{ margin: 2, padding: 1 }}>Click Me</Button>

// Solution: Define styles outside of render
const buttonStyles = { margin: 2, padding: 1 };
<Button sx={buttonStyles}>Click Me</Button>

Unexpected Theme Behavior

jsx
// Problem: Using theme without ThemeProvider
<Box sx={{ color: theme.palette.primary.main }}>Text</Box>

// Solution: Ensure ThemeProvider is wrapping your component
<ThemeProvider theme={theme}>
<Box sx={{ color: 'primary.main' }}>Text</Box>
</ThemeProvider>

Summary

Material UI is a powerful React component library that implements Google's Material Design. It provides:

  • A comprehensive set of pre-built, customizable components
  • Consistent styling and theming capabilities
  • Responsive design out of the box
  • Advanced styling options through various APIs
  • Good accessibility practices

With Material UI, developers can create professional-looking interfaces quickly without sacrificing customization or performance. The library's component-based approach aligns well with React's philosophy, making it a popular choice for React developers.

Additional Resources

To further explore Material UI:

  1. Official Material UI Documentation
  2. Material UI GitHub Repository
  3. Material Design Guidelines

Exercises

  1. Create a sign-up form with Material UI form components that includes validation.
  2. Build a responsive dashboard layout with AppBar, Drawer, and multiple Cards.
  3. Customize the Material UI theme to match a specific brand's color scheme.
  4. Create a data table with sorting, filtering, and pagination using Material UI's DataGrid component.
  5. Build a photo gallery with Material UI's ImageList component that works well on both desktop and mobile devices.

By practicing these exercises, you'll gain confidence using Material UI and be able to create sophisticated interfaces for your React applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)