React Material UI - herougo/SoftwareEngineerKnowledgeRepository GitHub Wiki

Sources:

Questions

How do you add css styles to material UI components?

  • answer: sx prop

Installation

https://mui.com/material-ui/getting-started/installation/

Please note that react and react-dom are peer dependencies, meaning you should ensure they are installed before installing Material UI.

"peerDependencies": {
  "react": "^17.0.0 || ^18.0.0",
  "react-dom": "^17.0.0 || ^18.0.0"
},

npm install @mui/material @emotion/react @emotion/styled

Typography

for text

import Typography from '@mui/material/Typography';

// body1 is the default variant
<Typography variant='h1'>My h1 heading text</Typography>
<Typography variant='h4' gutterBottom>My h4 heading text with bottom margin</Typography>
<Typography variant='subtitle1'>My subtitle1 text</Typography>
<Typography variant='subtitle2'>My subtitle2 text</Typography>
<Typography>My body1 text</Typography>
<Typography variant='body2'>My body2 text</Typography>

Button

import { Stack, Button, IconButton } from '@mui/material';
import SendIcon from '@mui/icons-material/Send';

<Stack spacing={2} direction='column'>
  <Button variant='text' onClick={() => alert('Clicked')}>Text</Button>
  <Button variant='contained'>Coloured button with white text</Button>
  <Button variant='outlined' color='primary' size='medium'>White button with coloured border</Button>
  <Button startIcon={<SendIcon />}>Send</Button>
  <Button endIcon={<SendIcon />}>Send</Button>
  <Button endIcon={<SendIcon />}>Send</Button>
  <IconButton aria-label='send'>
    <sendIcon />
  </IconButton>
</Stack>
// color can be primary (default), secondary, error warning, info, success, etc
// size can be small, medium (default), large
// startIcon puts icon before text, endIcon puts icon after text
// aria-label for accessibility

ButtonGroup

import { Stack, Button, ButtonGroup } from '@mui/material';

<ButtonGroup
  variant='contained'
  orientation='vertical'
  size='small'
  color='secondary'
  aria-label='alignment button group'
>
  <Button startIcon={<SendIcon />}>Send Top</Button>
  <Button endIcon={<SendIcon />}>Send Middle</Button>
  <Button endIcon={<SendIcon />}>Send Bottom</Button>
</ButtonGroup>

// buttons form a group where the only rounded corners are the top of the
//   top bottom and the bottom of the bottom button.

ToggleButton and ToggleButtonGroup

import { Stack, ToggleButton, ToggleButtonGroup } from '@mui/material';
import FormatBoldIcon from '@mui/icons-material/FormatBold';
import FormatItalicIcon from '@mui/icons-material/FormatItalic';

export const MuiButton = () => {
  const [formats, setFormats] = useState([])
  const handleFormatChange = (_event, updatedFormats) => { setFormats(updatedFormats) }

  return (
    <ToggleButtonGroup
      aria-label='text formatting'
      value={formats}
      onChange={handleFormatChange}
      color='secondary'
      size='medium'
    >
      <ToggleButton value='bold' aria-label='bold'><FormatBoldIcon /></ToggleButton>
      <ToggleButton value='italic' aria-label='italic'><FormatItalicIcon /></ToggleButton>
    </ToggleButtonGroup>
  )
}

TextField

Input text with label embedded in the text field (moves to the top when text is inputted).

See for what label does: https://mui.com/material-ui/react-text-field/

import { TextField, InputAdornment } from '@mui/material'

// InputAdorment is for prefixes/suffixes
<TextField
  label='Name'
  required
  helperText='I am text below the text field'
  value={value}
  onChange={e => setValue(e.target.value)}
  error={!value}
/>
<TextField label='Amount' InputProps={{
  startAdornment: <InputAdornment>$</InputAdornment>
}} />
<TextField label='Weight' InputProps={{
  endAdornment: <InputAdornment>kg</InputAdornment>
}} />

Select

import { TextField, MenuItem } from '@mui/material'

export const MuiButton = () => {
  const [countries, setCountries] = useState([])
  const handleFormatChange = (e) => {
    const value = e.target.value;
    setCountries(typeof value === 'string' ? value.split(',') : value);
  }
  // InputAdorment is for prefixes/suffixes
  <TextField
    label='Select Country'
    select
    value={country}
    onChange={handleChange}
    fullWidth
    SelectProps={{multiple:true}}
  >
    <MenuItem value='IN'>India</MenuItem>
    <MenuItem value='US'>USA</MenuItem>
    <MenuItem value='AU'>Australia</MenuItem>
  </TextField>
// fullWidth expands select width to parent

Radio

import { useState } from 'react'
import {
  Box,
  FormControl,
  FormLabel,
  FormControlLabel,
  RadioGroup,
  Radio
} from '@mui/material'

const MuiRadioButton = () => {
  const [value, setValue] = setState('')
  const handleChange = (event) => {
    setValue(event.target.value)
  }
  return (
    <Box>
      <FormControl>
        <FormLabel id='job-experience-group-label'>
          Years of Experience
        </FormLabel>
        <RadioGroup
          name='job-experience-group'
          aria-labelledby='job-experience-group-label'
          value={value}
          onChange={handleChange}
        >
          <FormControlLabel control={<Radio />} label='0-2' value='0-2' />
          <FormControlLabel control={<Radio />} label='3-5' value='3-5' />
          <FormControlLabel control={<Radio />} label='6-10' value='6-10' />
        </RadioGroup>
      </FormControl>
    </Box>
  )
}

Checkbox

import { useState } from 'react'
import {
  Box,
  FormControlLabel,
  Checkbox,
  FormControl,
  FormLabel,
  FormGroup
} from '@mui/material'
import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'
import BookmarkIcon from '@mui/icons-material/Bookmark'

export const MuiCheckbox = () => {
  const [acceptTnC, setAcceptTnC] = useState(false)
  const [skills, setSkills] = useState<string[]>([])
  console.log(skills)

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setAcceptTnC(event.target.checked)
  }
  const handleSkillChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const index = skills.indexOf(event.target.value)
    if (index === -1) {
      setSkills([...skills, event.target.value])
    } else {
      setSkills(skills.filter(skill => skill !== event.target.value))
    }
  }
  return (
    <Box>
      <Box>
        <FormControlLabel
          control={
            <Checkbox
              checked={acceptTnC}
              onChange={handleChange}
              size='small'
              color='secondary'
            />
          }
          label='Accept terms and conditions'
        />
      </Box>
      <Box>
        <Checkbox
          icon={<BookmarkBorderIcon />}
          checkedIcon={<BookmarkIcon />}
          checked={acceptTnC}
          onChange={handleChange}
        />
      </Box>
      <Box>
        <FormControl error>
          <FormLabel>Skills</FormLabel>
          <FormGroup>
            <FormControlLabel
              control={
                <Checkbox
                  value='html'
                  checked={skills.includes('html')}
                  onChange={handleSkillChange}
                />
              }
              label='HTML'
            />
            <FormControlLabel
              control={
                <Checkbox
                  value='css'
                  checked={skills.includes('css')}
                  onChange={handleSkillChange}
                />
              }
              label='CSS'
            />
            <FormControlLabel
              control={
                <Checkbox
                  value='javascript'
                  checked={skills.includes('javascript')}
                  onChange={handleSkillChange}
                />
              }
              label='JavaScript'
            />
          </FormGroup>
        </FormControl>
      </Box>
    </Box>
  )
}

Switch

import { Box, FormControlLabel, Switch } from '@mui/material'
import { useState } from 'react'

export const MuiSwitch = () => {
  const [checked, setChecked] = useState(false)
  console.log(checked)
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setChecked(event.target.checked)
  }
  return (
    <Box>
      <FormControlLabel
        control={<Switch checked={checked} onChange={handleChange} />}
        label='Dark mode'
      />
    </Box>
  )
}

Rating

import { Stack, Rating } from '@mui/material'
import { useState } from 'react'
import FavoriteIcon from '@mui/icons-material/Favorite'
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'

export const MuiRating = () => {
  const [value, setValue] = useState<number | null>(3)
  console.log(value)
  const handleChange = (
    _event: React.ChangeEvent<{}>,
    newValue: number | null
  ) => {
    setValue(newValue)
  }
  return (
    <Stack spacing={2}>
      <Rating
        value={value}
        onChange={handleChange}
        precision={0.5}
        size='large'
        icon={<FavoriteIcon fontSize='inherit' color='error' />}
        emptyIcon={<FavoriteBorderIcon fontSize='inherit' />}
        readOnly
      />
    </Stack>
  )
}

Autocomplete

import { Stack, Autocomplete, TextField } from '@mui/material'
import { useState } from 'react'

type Skill = {
  id: number
  label: string
}

const skills = ['HTML', 'CSS', 'JavaScript', 'TypeScript', 'React']
const skillsOptions = skills.map((skill, index) => ({
  id: index + 1,
  label: skill
}))

export const MuiAutocomplete = () => {
  const [value, setValue] = useState<string | null>(null)
  const [skill, setSkill] = useState<Skill | null>(null)
  console.log(skill)
  return (
    <Stack spacing={2} width='250px'>
      <Autocomplete
        options={skills}
        renderInput={params => <TextField {...params} label='Skills' />}
        value={value}
        onChange={(event: any, newValue: string | null) => {
          setValue(newValue)
        }}
      />
      <Autocomplete
        options={skillsOptions}
        renderInput={params => <TextField {...params} label='Skills' />}
        value={skill}
        onChange={(_event: any, newValue: Skill | null) => {
          setSkill(newValue)
        }}
      />
    </Stack>
  )
}

Paper, Box, Grid, Stack

The Paper component is a container for displaying content on an elevated surface.

The Box component is a generic container for grouping other components. It's a fundamental building block when working with Material UI—you can think of it as a

with extra built-in features, like access to your app's theme and the sx prop.

Stack is a container component for arranging elements vertically or horizontally.

import { Box, Stack, Grid, Paper } from '@mui/material'

export const MuiLayout = () => {
  return (
    <Paper sx={{ padding: '32px' }} elevation={2}>
      <Stack border='1px solid' spacing={2} direction='row'>
        <Box
          component='span'
          sx={{
            backgroundColor: 'primary.main',
            color: 'white',
            height: '100px',
            width: '100px',
            padding: '16px',
            '&:hover': {
              backgroundColor: 'primary.light'
            }
          }}>
          Codevolution
        </Box>
        <Box
          display='flex'
          height='100px'
          width='100px'
          bgcolor='success.light'
          p={2}></Box>
      </Stack>
      <Grid rowSpacing={2} columnSpacing={1} container my={4}>
        <Grid item xs={6}>
          <Box p={2} bgcolor='primary.light'>
            Item 1
          </Box>
        </Grid>
        <Grid item xs={6}>
          <Box p={2} bgcolor='primary.light'>
            Item 2
          </Box>
        </Grid>
        <Grid item xs={6}>
          <Box p={2} bgcolor='primary.light'>
            Item 3
          </Box>
        </Grid>
        <Grid item xs={6}>
          <Box p={2} bgcolor='primary.light'>
            Item 4
          </Box>
        </Grid>
      </Grid>
    </Paper>
  )
}

Card

import {
  Box,
  Card,
  CardContent,
  CardActions,
  Typography,
  Button,
  CardMedia
} from '@mui/material'

export const MuiCard = () => {
  return (
    <Box width='300px'>
      <Card>
        <CardMedia
          component='img'
          height='140'
          image='https://source.unsplash.com/random'
          alt='unsplash image'
        />
        <CardContent>
          <Typography gutterBottom variant='h5' component='div'>
            React
          </Typography>
          <Typography variant='body2' color='text.secondary'>
            React is a JavaScript library for building user interfaces. React
            can be used as a base in the development of single-page or mobile
            applications.
          </Typography>
        </CardContent>
        <CardActions>
          <Button size='small'>Share</Button>
          <Button size='small'>Learn More</Button>
        </CardActions>
      </Card>
    </Box>
  )
}

Accordion

Accordion = Collapsible thing

import {
  Accordion,
  AccordionSummary,
  AccordionDetails,
  Typography
} from '@mui/material'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { useState } from 'react'

export const MuiAccordion = () => {
  const [expanded, setExpanded] = useState<string | false>(false)
  const handleChange = (isExpanded: boolean, panel: string) => {
    setExpanded(isExpanded ? panel : false)
  }
  return (
    <>
      <Accordion
        expanded={expanded === 'panel1'}
        onChange={(event, isExpanded) => handleChange(isExpanded, 'panel1')}>
        <AccordionSummary
          aria-controls='panel1-content'
          id='panel1-header'
          expandIcon={<ExpandMoreIcon />}>
          <Typography>Accordion 1</Typography>
        </AccordionSummary>
        <AccordionDetails>
          <Typography>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
            malesuada lacus ex, sit amet blandit leo lobortis eget.
          </Typography>
        </AccordionDetails>
      </Accordion>
      <Accordion
        expanded={expanded === 'panel2'}
        onChange={(event, isExpanded) => handleChange(isExpanded, 'panel2')}>
        <AccordionSummary
          aria-controls='panel2-content'
          id='panel2-header'
          expandIcon={<ExpandMoreIcon />}>
          <Typography>Accordion 2</Typography>
        </AccordionSummary>
        <AccordionDetails>
          <Typography>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
            malesuada lacus ex, sit amet blandit leo lobortis eget.
          </Typography>
        </AccordionDetails>
      </Accordion>
    </>
  )
}

ImageList

(lazy loading means loaded as needed (i.e. in view))

import { Stack, ImageList, ImageListItem, Box } from '@mui/material'
export const MuiImageList = () => {
  return (
    <Stack spacing={4}>
      <ImageList sx={{ width: 500, height: 450 }} cols={3} rowHeight={164}>
        {itemData.map(item => (
          <ImageListItem key={item.img}>
            <img
              src={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2`}
              alt={item.title}
              loading='lazy'
            />
          </ImageListItem>
        ))}
      </ImageList>
      <ImageList
        sx={{ width: 500, height: 450 }}
        variant='woven'
        cols={3}
        gap={8}>
        {itemData2.map(item => (
          <ImageListItem key={item.img}>
            <img
              src={`${item.img}?w=164&h=164&fit=crop&auto=format&dpr=2`}
              alt={item.title}
              loading='lazy'
            />
          </ImageListItem>
        ))}
      </ImageList>
      <Box sx={{ width: 500, height: 450, overflowY: 'scroll' }}>
        <ImageList variant='masonry' cols={3} gap={8}>
          {itemData3.map(item => (
            <ImageListItem key={item.img}>
              <img
                src={`${item.img}?w=248&fit=crop&auto=format&dpr=2`}
                alt={item.title}
                loading='lazy'
              />
            </ImageListItem>
          ))}
        </ImageList>
      </Box>
    </Stack>
  )
}

const itemData = [
  {
    img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e',
    title: 'Breakfast'
  },
  ...
]

Navbar, Menu

Menu - popup display of options next to something

import {
  AppBar,
  Toolbar,
  IconButton,
  Typography,
  Button,
  Stack,
  Menu,
  MenuItem
} from '@mui/material'
import CatchingPokemonIcon from '@mui/icons-material/CatchingPokemon'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
import { useState } from 'react'

export const MuiNavbar = () => {
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
  const open = Boolean(anchorEl)
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    setAnchorEl(event.currentTarget)
  }
  const handleClose = () => {
    setAnchorEl(null)
  }
  return (
    <AppBar position='static' color='transparent'>
      <Toolbar>
        <IconButton size='large' edge='start' color='inherit' aria-label='logo'>
          <CatchingPokemonIcon />
        </IconButton>
        <Typography variant='h6' component='div' sx={{ flexGrow: 1 }}>
          POKEMONAPP
        </Typography>
        <Stack direction='row' spacing={2}>
          <Button color='inherit'>Features</Button>
          <Button color='inherit'>Pricing</Button>
          <Button color='inherit'>About</Button>
          <Button
            color='inherit'
            id='resources-button'
            aria-controls={open ? 'resources-menu' : undefined}
            aria-haspopup='true'
            aria-expanded={open ? 'true' : undefined}
            endIcon={<KeyboardArrowDownIcon />}
            onClick={handleClick}>
            Resources
          </Button>
          <Button color='inherit'>Login</Button>
        </Stack>
        <Menu
          id='resources-menu'
          anchorEl={anchorEl}
          open={open}
          onClose={handleClose}
          anchorOrigin={{
            vertical: 'bottom',
            horizontal: 'right'
          }}
          transformOrigin={{
            vertical: 'top',
            horizontal: 'right'
          }}
          MenuListProps={{
            'aria-labelledby': 'resources-button'
          }}>
          <MenuItem onClick={handleClose}>Blog</MenuItem>
          <MenuItem onClick={handleClose}>Podcast</MenuItem>
        </Menu>
      </Toolbar>
    </AppBar>
  )
}

Link

import { Stack, Link, Typography } from '@mui/material'

export const MuiLink = () => {
  return (
    <Stack spacing={2} m={4} direction='row'>
      <Typography variant='h6'>
        <Link href='#'>Link</Link>
      </Typography>
      <Link href='#' color='secondary' underline='hover'>
        Secondary
      </Link>
      <Link href='#' variant='body2' underline='none'>
        Body 2 link
      </Link>
    </Stack>
  )
}

Breadcrumbs

When displaying a path with each section of the path clickable.

Example:

Home / Catalog / Accessories / New Collection

import { Box, Breadcrumbs, Link, Typography } from '@mui/material'
import NavigateNextIcon from '@mui/icons-material/NavigateNext'

export const MuiBreadcrumbs = () => {
  return (
    <Box m={2}>
      <Breadcrumbs
        separator={<NavigateNextIcon fontSize='small' />}
        maxItems={3}
        itemsAfterCollapse={2}
        aria-label='breadcrumb'>
        <Link underline='hover' href='#'>
          Home
        </Link>
        <Link underline='hover' href='#'>
          Catalog
        </Link>
        <Link underline='hover' href='#'>
          Accessories
        </Link>
        <Link underline='hover' href='#'>
          New Collection
        </Link>
        <Typography color='text.primary'>Shoes</Typography>
      </Breadcrumbs>
    </Box>
  )
}

Drawer

i.e. sliding section from hamburger buttons

import { Drawer, Box, Typography, IconButton } from '@mui/material'
import { useState } from 'react'
import MenuIcon from '@mui/icons-material/Menu'

export const MuiDrawer = () => {
  const [isDrawerOpen, setIsDrawerOpen] = useState(false)

  return (
    <>
      <IconButton
        onClick={() => setIsDrawerOpen(true)}
        size='large'
        edge='start'
        color='inherit'
        aria-label='logo'>
        <MenuIcon />
      </IconButton>
      <Drawer
        anchor='left'
        open={isDrawerOpen}
        onClose={() => setIsDrawerOpen(false)}>
        <Box p={2} width='250px' role='presentation' textAlign='center'>
          <Typography variant='h6' component='div'>
            Side Panel
          </Typography>
        </Box>
      </Drawer>
    </>
  )
}

SpeedDial

i.e. + circle button which expands to other icon buttons (usually used on mobile)

import { SpeedDial, SpeedDialAction, SpeedDialIcon } from '@mui/material'
import CopyIcon from '@mui/icons-material/FileCopyOutlined'
import PrintIcon from '@mui/icons-material/Print'
import ShareIcon from '@mui/icons-material/Share'
import EditIcon from '@mui/icons-material/Edit'

export const MuiSpeedDial = () => {
  return (
    <SpeedDial
      ariaLabel='Navigation speed dial'
      sx={{ position: 'absolute', bottom: 16, right: 16 }}
      icon={<SpeedDialIcon openIcon={<EditIcon />} />}>
      <SpeedDialAction icon={<CopyIcon />} tooltipTitle='Copy' tooltipOpen />
      <SpeedDialAction icon={<PrintIcon />} tooltipTitle='Print' tooltipOpen />
      <SpeedDialAction icon={<ShareIcon />} tooltipTitle='Share' tooltipOpen />
    </SpeedDial>
  )
}

BottomNavigation

used more for mobile

import { BottomNavigation, BottomNavigationAction } from '@mui/material'
import HomeIcon from '@mui/icons-material/Home'
import FavoriteIcon from '@mui/icons-material/Favorite'
import PersonIcon from '@mui/icons-material/Person'
import { useState } from 'react'

export const MuiBottomNavigation = () => {
  const [value, setValue] = useState(0)
  return (
    <BottomNavigation
      sx={{ width: '100%', position: 'absolute', bottom: 0 }}
      showLabels
      value={value}
      onChange={(event, newValue) => {
        setValue(newValue)
      }}>
      <BottomNavigationAction label='Home' icon={<HomeIcon />} />
      <BottomNavigationAction label='Favorites' icon={<FavoriteIcon />} />
      <BottomNavigationAction label='Profile' icon={<PersonIcon />} />
    </BottomNavigation>
  )
}

Avatar

circular profile pictures

import { Stack, Avatar, AvatarGroup } from '@mui/material'
export const MuiAvatar = () => {
  return (
    <Stack spacing={4}>
      <Stack direction='row' spacing={1}>
        <Avatar sx={{ bgcolor: 'primary.light' }}>BW</Avatar>
        <Avatar sx={{ bgcolor: 'success.light' }}>CK</Avatar>
      </Stack>
      <Stack direction='row' spacing={1}>
        <AvatarGroup max={3}>
          <Avatar sx={{ bgcolor: 'primary.light' }}>BW</Avatar>
          <Avatar sx={{ bgcolor: 'success.light' }}>CK</Avatar>
          <Avatar
            src='https://randomuser.me/api/portraits/women/79.jpg'
            alt='Jane Doe'
          />
          <Avatar
            src='https://randomuser.me/api/portraits/men/51.jpg'
            alt='John Doe'
          />
        </AvatarGroup>
      </Stack>
      <Stack direction='row' spacing={1}>
        <Avatar
          variant='rounded'
          sx={{ bgcolor: 'primary.light', width: 48, height: 48 }}>
          BW
        </Avatar>
        <Avatar
          variant='rounded'
          sx={{ bgcolor: 'success.light', width: 48, height: 48 }}>
          CK
        </Avatar>
      </Stack>
    </Stack>
  )
}

Badge

e.g. showing how many notifications you got (circle on top right of icon)

import { Stack, Badge } from '@mui/material'
import MailIcon from '@mui/icons-material/Mail'

export const MuiBadge = () => {
  return (
    <Stack spacing={2} direction='row'>
      <Badge badgeContent={5} color='secondary'>
        <MailIcon />
      </Badge>
      <Badge badgeContent={0} color='secondary' showZero>
        <MailIcon />
      </Badge>
      <Badge badgeContent={100} color='secondary' max={999}>
        <MailIcon />
      </Badge>
      <Badge color='secondary' variant='dot'>
        <MailIcon />
      </Badge>
    </Stack>
  )
}

List

e.g. Email List

import {
  Box,
  List,
  ListItem,
  ListItemText,
  ListItemButton,
  ListItemIcon,
  Divider,
  ListItemAvatar,
  Avatar
} from '@mui/material'
import InboxIcon from '@mui/icons-material/Inbox'

export const MuiList = () => {
  return (
    <Box sx={{ width: '400px', bgcolor: '#efefef' }}>
      <List>
        <ListItem disablePadding>
          <ListItemButton>
            <ListItemIcon>
              <InboxIcon />
            </ListItemIcon>
            <ListItemText primary='List item 1' />
          </ListItemButton>
        </ListItem>
        <Divider />
        <ListItem>
          <ListItemAvatar>
            <Avatar>
              <InboxIcon />
            </Avatar>
          </ListItemAvatar>
          <ListItemText primary='List item 2' secondary='Secondary text' />
        </ListItem>
        <ListItem>
          <ListItemText primary='List item 3' />
        </ListItem>
      </List>
    </Box>
  )
}

Chip

Pill-shaped things which can be used for (possibly closeable) tags

import { Stack, Chip, Avatar } from '@mui/material'
import { useState } from 'react'

export const MuiChip = () => {
  const [chips, setChips] = useState(['Chip 1', 'Chip 2', 'Chip 3'])
  const handleDelete = (chipToDelete: string) => {
    setChips(chips => chips.filter(chip => chip !== chipToDelete))
  }
  return (
    <Stack direction='row' spacing={1}>
      <Chip label='Chip' color='primary' size='small' />
      <Chip
        label='Chip Outlined'
        variant='outlined'
        color='secondary'
        avatar={<Avatar>V</Avatar>}
      />
      <Chip label='Click' color='success' onClick={() => alert('Clicked')} />
      <Chip
        label='Delete'
        color='error'
        onClick={() => alert('Clicked')}
        onDelete={() => alert('Delete')}
      />
      {chips.map(chip => (
        <Chip key={chip} label={chip} onDelete={() => handleDelete(chip)} />
      ))}
    </Stack>
  )
}

Tooltip

For example, when you hover over an edit icon, the "Edit" text shows up to describe it.

import { Tooltip, IconButton } from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete'

export const MuiTooltip = () => {
  return (
    <Tooltip
      title='Delete'
      placement='right'
      arrow
      enterDelay={500}
      leaveDelay={200}>
      <IconButton>
        <DeleteIcon />
      </IconButton>
    </Tooltip>
  )
}

Table

import {
  TableContainer,
  Table,
  TableHead,
  TableBody,
  TableRow,
  TableCell,
  Paper
} from '@mui/material'

export const MuiTable = () => {
  return (
    <TableContainer sx={{ maxHeight: '300px' }} component={Paper}>
      <Table stickyHeader aria-label='simple table'>
        <TableHead>
          <TableRow>
            <TableCell>Id</TableCell>
            <TableCell>First Name</TableCell>
            <TableCell>Last Name</TableCell>
            <TableCell align='center'>Email</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {tableData.map(row => (
            <TableRow
              key={row.id}
              sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
              <TableCell>{row.id}</TableCell>
              <TableCell>{row.first_name}</TableCell>
              <TableCell>{row.last_name}</TableCell>
              <TableCell align='center'>{row.email}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  )
}

const tableData = [
  {
    id: 1,
    first_name: 'Beret',
    last_name: 'Lennard',
    email: '[email protected]',
    gender: 'Female',
    ip_address: '213.196.192.52'
  },
  ...
]

Alert

import { Stack, Alert, AlertTitle, Button } from '@mui/material'
import CheckIcon from '@mui/icons-material/Check'
export const MuiAlert = () => {
  return (
    <Stack spacing={2}>
      <Alert severity='error' onClose={() => alert('Close alert')}>
        <AlertTitle>Error</AlertTitle>This is an error alert
      </Alert>
      <Alert severity='warning'>
        <AlertTitle>Warning</AlertTitle>This is a warning alert
      </Alert>
      <Alert severity='info'>
        <AlertTitle>Info</AlertTitle>This is an info alert
      </Alert>
      <Alert
        severity='success'
        icon={<CheckIcon fontSize='inherit' />}
        action={
          <Button color='inherit' size='small'>
            UNDO
          </Button>
        }>
        <AlertTitle>Success</AlertTitle>This is a success alert
      </Alert>
      <Alert variant='outlined' severity='error'>
        This is an outlined error alert
      </Alert>
      <Alert variant='filled' severity='error'>
        This is an filled error alert
      </Alert>
    </Stack>
  )
}

Snackbar

e.g. when you send an email and a popup shows up saying the email is sent

import { Snackbar, Button, Alert, AlertProps } from '@mui/material'
import { useState, forwardRef } from 'react'

const SnackbarAlert = forwardRef<HTMLDivElement, AlertProps>(
  function SnackbarAlert(props, ref) {
    return <Alert elevation={6} ref={ref} {...props} />
  }
)

export const MuiSnackbar = () => {
  const [open, setOpen] = useState(false)
  const handleClose = (
    event?: React.SyntheticEvent | Event,
    reason?: string
  ) => {
    if (reason === 'clickaway') {
      return
    }
    setOpen(false)
  }
  return (
    <>
      <Button onClick={() => setOpen(true)}>Submit</Button>
      <Snackbar
        open={open}
        autoHideDuration={4000}
        onClose={handleClose}
        message='Form submitted successfully!'
        anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
      />
      <Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
        <SnackbarAlert onClose={handleClose} severity='success'>
          Form submitted successfully!
        </SnackbarAlert>
      </Snackbar>
    </>
  )
}

Dialog

i.e. popup / modal

import {
  Button,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogContentText,
  DialogActions
} from '@mui/material'
import { useState } from 'react'

export const MuiDialog = () => {
  const [open, setOpen] = useState(false)
  return (
    <>
      <Button onClick={() => setOpen(true)}>Open dialog</Button>
      <Dialog
        open={open}
        onClose={() => setOpen(false)}
        aria-labelledby='dialog-title'
        aria-describedby='dialog-description'>
        <DialogTitle id='dialog-title'>Submit the test?</DialogTitle>
        <DialogContent>
          <DialogContentText id='dialog-description'>
            Are you sure you want to submit the test? You will not be able to
            edit it after submitting.
          </DialogContentText>
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setOpen(false)}>Cancel</Button>
          <Button onClick={() => setOpen(false)} autoFocus>
            Submit
          </Button>
        </DialogActions>
      </Dialog>
    </>
  )
}

CircularProgress

import { Stack, CircularProgress, LinearProgress } from '@mui/material'
export const MuiProgress = () => {
  return (
    <Stack spacing={2}>
      <CircularProgress />
      <CircularProgress color='success' />
      <CircularProgress variant='determinate' value={60} />
      <LinearProgress />
      <LinearProgress color='success' />
      <LinearProgress variant='determinate' value={60} />
    </Stack>
  )
}

Skeleton

Flashing grey background as substitute when things are loading

import { Avatar, Box, Skeleton, Stack, Typography } from '@mui/material'
import { useState, useEffect } from 'react'

export const MuiSkeleton = () => {
  const [loading, setLoading] = useState(true)
  useEffect(() => {
    setTimeout(() => {
      setLoading(false)
    }, 3000)
  }, [])
  return (
    <Box sx={{ width: '250px' }}>
      {loading ? (
        <Skeleton
          variant='rectangular'
          width={256}
          height={144}
          animation='wave'
        />
      ) : (
        <img
          src='https://source.unsplash.com/random/256x144'
          alt='skeleton'
          width={256}
          height={144}
        />
      )}

      <Stack
        direction='row'
        spacing={1}
        sx={{ width: '100%', marginTop: '12px' }}>
        {loading ? (
          <Skeleton
            variant='circular'
            width={40}
            height={40}
            animation='wave'
          />
        ) : (
          <Avatar>V</Avatar>
        )}

        <Stack sx={{ width: '80%' }}>
          {loading ? (
            <>
              <Typography variant='body1'>
                <Skeleton variant='text' width='100%' animation='wave' />
              </Typography>
              <Typography variant='body2'>
                <Skeleton variant='text' width='100%' animation='wave' />
              </Typography>
            </>
          ) : (
            <>
              <Typography variant='body1'>React MUI Tutorial</Typography>
            </>
          )}
        </Stack>
      </Stack>
    </Box>
  )
}

Material UI Lab

Loading Button

useful for submit buttons which you don't want to click twice

import { Stack } from '@mui/material'
import { LoadingButton } from '@mui/lab'
import SaveIcon from '@mui/icons-material/Save'

export const MuiLoadingButton = () => {
  return (
    <Stack direction='row' spacing={2}>
      <LoadingButton variant='outlined'>Submit</LoadingButton>
      <LoadingButton loading variant='outlined'>
        Submit
      </LoadingButton>
      <LoadingButton loadingIndicator='Loading...' variant='outlined'>
        Fetch data
      </LoadingButton>
      <LoadingButton loading loadingIndicator='Loading...' variant='outlined'>
        Fetch data
      </LoadingButton>
      <LoadingButton
        loadingPosition='start'
        startIcon={<SaveIcon />}
        variant='outlined'>
        Save
      </LoadingButton>
      <LoadingButton
        loading
        loadingPosition='start'
        startIcon={<SaveIcon />}
        variant='outlined'>
        Save
      </LoadingButton>
    </Stack>
  )
}

DatePicker, TimePicker, DateTimePicker

import { Stack, TextField } from '@mui/material'
import { DatePicker, TimePicker, DateTimePicker } from '@mui/lab'
import { useState } from 'react'

export const MuiDateTimePicker = () => {
  const [selectedDate, setSelectedDate] = useState<Date | null>(null)
  const [selectedTime, setSelectedTime] = useState<Date | null>(null)
  const [selectedDateTime, setSelectedDateTime] = useState<Date | null>(null)

  console.log({
    selectedDate,
    selectedTime: selectedTime && selectedTime.toLocaleTimeString(),
    selectedDateTime
  })
  return (
    <Stack spacing={4} sx={{ width: '250px' }}>
      <DatePicker
        label='Date Picker'
        value={selectedDate}
        onChange={newValue => {
          setSelectedDate(newValue)
        }}
        renderInput={params => <TextField {...params} />}
      />
      <TimePicker
        label='Time Picker'
        value={selectedTime}
        onChange={newValue => {
          setSelectedTime(newValue)
        }}
        renderInput={params => <TextField {...params} />}
      />
      <DateTimePicker
        label='Date Time Picker'
        value={selectedDateTime}
        onChange={newValue => {
          setSelectedDateTime(newValue)
        }}
        renderInput={params => <TextField {...params} />}
      />
    </Stack>
  )
}

DateRangePicker

import { Box, TextField } from '@mui/material'
import { DateRangePicker, DateRange } from '@mui/lab'
import { useState } from 'react'

export const MuiDateRangePicker = () => {
  const [value, setValue] = useState<DateRange<Date>>([null, null])
  console.log('value', value)
  return (
    <Box width='500px'>
      <DateRangePicker
        startText='Check-in'
        endText='Check-out'
        value={value}
        onChange={newValue => {
          setValue(newValue)
        }}
        renderInput={(startProps, endProps) => (
          <>
            <TextField {...startProps} />
            <Box sx={{ mx: 2 }}> to </Box>
            <TextField {...endProps} />
          </>
        )}
      />
    </Box>
  )
}

Tabs

import { Box, Tab } from '@mui/material'
import { TabContext, TabList, TabPanel } from '@mui/lab'
import { useState } from 'react'
import FavoriteIcon from '@mui/icons-material/Favorite'

export const MuiTabs = () => {
  const [value, setValue] = useState('1')

  const handleChange = (event: React.SyntheticEvent, newValue: string) => {
    setValue(newValue)
  }
  return (
    <Box sx={{ width: '300px' }}>
      <TabContext value={value}>
        <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
          <TabList
            onChange={handleChange}
            aria-label='Tabs example'
            textColor='secondary'
            indicatorColor='secondary'
            centered
            variant='scrollable'
            scrollButtons='auto'>
            <Tab
              icon={<FavoriteIcon />}
              iconPosition='start'
              label='Tab One'
              value='1'
            />
            <Tab label='Tab Two' value='2' disabled />
            <Tab label='Tab Three' value='3' />
            <Tab label='Tab Four' value='4' />
            <Tab label='Tab Five' value='5' />
            <Tab label='Tab Six' value='6' />
          </TabList>
        </Box>
        <TabPanel value='1'>Item One</TabPanel>
        <TabPanel value='2'>Item Two</TabPanel>
        <TabPanel value='3'>Item Three</TabPanel>
        <TabPanel value='4'>Item Four</TabPanel>
        <TabPanel value='5'>Item Five</TabPanel>
        <TabPanel value='6'>Item Six</TabPanel>
      </TabContext>
    </Box>
  )
}

Timeline

import {
  Timeline,
  TimelineItem,
  TimelineSeparator,
  TimelineConnector,
  TimelineContent,
  TimelineDot,
  TimelineOppositeContent
} from '@mui/lab'
export const MuiTimeline = () => {
  return (
    <Timeline>
      <TimelineItem>
        <TimelineOppositeContent color='text.secondary'>
          09:30 am
        </TimelineOppositeContent>
        <TimelineSeparator>
          <TimelineDot color='secondary' variant='outlined' />
          <TimelineConnector />
        </TimelineSeparator>
        <TimelineContent>Eat</TimelineContent>
      </TimelineItem>
      <TimelineItem>
        <TimelineOppositeContent color='text.secondary'>
          10:30 am
        </TimelineOppositeContent>
        <TimelineSeparator>
          <TimelineDot color='secondary' variant='outlined' />
          <TimelineConnector />
        </TimelineSeparator>
        <TimelineContent>Code</TimelineContent>
      </TimelineItem>
      <TimelineItem>
        <TimelineOppositeContent color='text.secondary'>
          10:30 am
        </TimelineOppositeContent>
        <TimelineSeparator>
          <TimelineDot color='secondary' variant='outlined' />
          <TimelineConnector />
        </TimelineSeparator>
        <TimelineContent>Sleep</TimelineContent>
      </TimelineItem>
      <TimelineItem>
        <TimelineOppositeContent color='text.secondary'>
          09:00 am
        </TimelineOppositeContent>
        <TimelineSeparator>
          <TimelineDot color='secondary' variant='outlined' />
        </TimelineSeparator>
        <TimelineContent>Repeat</TimelineContent>
      </TimelineItem>
    </Timeline>
  )
}

Masonry

display of N rectangular elements (with variable heights) in fixed length columns

import { Masonry } from '@mui/lab'
import {
  Box,
  Paper,
  Accordion,
  AccordionSummary,
  AccordionDetails,
  Typography
} from '@mui/material'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'

const heights = [
  150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80
]

export const MuiMasonry = () => {
  return (
    <Box sx={{ width: 500, minHeight: 400 }}>
      <Masonry columns={4} spacing={1}>
        {heights.map((height, index) => (
          <Paper
            key={index}
            sx={{
              //   display: 'flex',
              //   justifyContent: 'center',
              //   alignItems: 'center',
              //   height,
              border: '1px solid'
            }}>
            <Accordion sx={{ minHeight: height }}>
              <AccordionSummary expandIcon={<ExpandMoreIcon />}>
                <Typography>Accordion {index + 1}</Typography>
              </AccordionSummary>
              <AccordionDetails>Contents</AccordionDetails>
            </Accordion>
          </Paper>
        ))}
      </Masonry>
    </Box>
  )
}

Extra Topics

Responsiveness

<Box
  sx={{
    width: {
      xs: 100, // 0 px
      sm: 200, // 600 px
      md: 300, // 900 px
      lg: 400, // 1200 px
      xl: 500, // 1536 px
    }
  }} />

Custom Themes

import { createTheme } from '@mui/material'

const theme = createTheme({
  palette: {
    secondary: {
      main: colors.orange[500]
    }
  }
})

function App() {
  return (
    <ThemeProvider theme={theme}>
      <div className='App'>
        ...
      </div>
    </ThemeProvider>
  )
}
⚠️ **GitHub.com Fallback** ⚠️