Skip to contents
This page is an adaptation of the MUI Material UI documentation pages on theming, palette, dark mode, typography and themed components.

Theming

Customize Material UI with your theme. You can change the colors, the typography and much more.

The theme specifies the color of the components, darkness of the surfaces, level of shadow, appropriate opacity of ink elements, etc.

Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.

To promote greater consistency between apps, light and dark theme types are available to choose from. By default, components use the light theme type.

Theme provider

Material UI components adhere to the library’s default theme out of the box. Use ThemeProvider() to inject a custom theme into your application.

In React, you build a theme with createTheme(options) and pass it to ThemeProvider. In R, ThemeProvider(theme = ...) takes the options as a named list and calls createTheme() for you. ThemeProvider() must be a parent of the components you want to customize:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(palette = list(primary = list(main = "#6a1b9a"))),
    Stack(
      direction = "row",
      spacing = 2,
      Button(variant = "contained", "Themed button"),
      Chip(color = "primary", label = "Themed chip"),
      Switch(defaultChecked = TRUE)
    )
  )
)

In a Shiny app, wrap the whole UI:

ui <- muiMaterialPage(
  ThemeProvider(
    theme = myTheme,
    CssBaseline(),
    # the rest of your app
  )
)

Theme configuration variables

Changing the theme configuration variables is the most effective way to match Material UI to your needs. The following sections cover the most important theme variables:

You can check out the default theme to view the default theme in full.

Custom variables

It can be convenient to add additional variables to the theme so you can use them everywhere. For instance, a status color. In sx, a function of the theme (written with JS()) reads the variable:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(status = list(danger = "#ff9800")), # orange[500]
    Checkbox(defaultChecked = TRUE, sx = JS("(theme) => ({ color: theme.status.danger, '&.Mui-checked': { color: theme.status.danger } })"))
  )
)
JS code
import Checkbox from '@mui/material/Checkbox';
import { createTheme, ThemeProvider, styled } from '@mui/material/styles';
import { orange } from '@mui/material/colors';

const CustomCheckbox = styled(Checkbox)(({ theme }) => ({
  color: theme.status.danger,
  '&.Mui-checked': {
    color: theme.status.danger,
  },
}));

const theme = createTheme({
  status: {
    danger: orange[500],
  },
});

export default function CustomStyles() {
  return (
    <ThemeProvider theme={theme}>
      <CustomCheckbox defaultChecked />
    </ThemeProvider>
  );
}

Palette

The palette enables you to modify the color of the components to suit your brand.

Default colors

The theme exposes the following default palette colors (accessible under theme.palette.*, and usable as "primary.main", "error.light", … in sx):

  • primary - for primary interface elements.
  • secondary - for secondary interface elements.
  • error - for elements that the user should be made aware of.
  • warning - for potentially dangerous actions or important messages.
  • info - for highlighting neutral information.
  • success - for indicating the successful completion of an action that the user triggered.

Each color has a main value, and light, dark and contrastText values derived from it:

intentions <- c("primary", "secondary", "error", "warning", "info", "success")

muiMaterialPage(
  CssBaseline(),
  Grid(
    container = TRUE,
    spacing = 2,
    lapply(intentions, function(intention) {
      Grid(
        size = list(xs = 6, sm = 4),
        Typography(variant = "subtitle2", intention),
        lapply(c("light", "main", "dark"), function(tone) {
          Box(
            sx = list(
              bgcolor = paste0(intention, ".", tone),
              color = paste0(intention, ".contrastText"),
              p = 1,
              typography = "body2"
            ),
            tone
          )
        })
      )
    })
  )
)
JS code
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { useTheme, rgbToHex, styled } from '@mui/material/styles';

const Group = styled(Typography)(({ theme }) => ({
  marginTop: theme.spacing(3),
}));

const Color = styled(Grid)(({ theme }) => ({
  display: 'flex',
  alignItems: 'center',
  '& div:first-of-type': {
    width: theme.spacing(6),
    height: theme.spacing(6),
    marginRight: theme.spacing(1),
    borderRadius: theme.shape.borderRadius,
    boxShadow: 'inset 0 2px 4px 0 rgba(0, 0, 0, .06)',
  },
}));

export default function Intentions() {
  const theme = useTheme();

  const item = (color, name) => (
    <Color size={{ xs: 12, sm: 6, md: 4 }}>
      <div style={{ backgroundColor: color }} />
      <div>
        <Typography variant="body2">{name}</Typography>
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {rgbToHex(color)}
        </Typography>
      </div>
    </Color>
  );

  return (
    <Box sx={{ width: '100%' }}>
      <Group gutterBottom>Primary</Group>
      <Grid container spacing={2}>
        {item(theme.palette.primary.light, 'palette.primary.light')}
        {item(theme.palette.primary.main, 'palette.primary.main')}
        {item(theme.palette.primary.dark, 'palette.primary.dark')}
      </Grid>
      <Group gutterBottom>Secondary</Group>
      <Grid container spacing={2}>
        {item(theme.palette.secondary.light, 'palette.secondary.light')}
        {item(theme.palette.secondary.main, 'palette.secondary.main')}
        {item(theme.palette.secondary.dark, 'palette.secondary.dark')}
      </Grid>
      <Group gutterBottom>Error</Group>
      <Grid container spacing={2}>
        {item(theme.palette.error.light, 'palette.error.light')}
        {item(theme.palette.error.main, 'palette.error.main')}
        {item(theme.palette.error.dark, 'palette.error.dark')}
      </Grid>
      <Group gutterBottom>Warning</Group>
      <Grid container spacing={2}>
        {item(theme.palette.warning.light, 'palette.warning.light')}
        {item(theme.palette.warning.main, 'palette.warning.main')}
        {item(theme.palette.warning.dark, 'palette.warning.dark')}
      </Grid>
      <Group gutterBottom>Info</Group>
      <Grid container spacing={2}>
        {item(theme.palette.info.light, 'palette.info.light')}
        {item(theme.palette.info.main, 'palette.info.main')}
        {item(theme.palette.info.dark, 'palette.info.dark')}
      </Grid>
      <Group gutterBottom>Success</Group>
      <Grid container spacing={2}>
        {item(theme.palette.success.light, 'palette.success.light')}
        {item(theme.palette.success.main, 'palette.success.main')}
        {item(theme.palette.success.dark, 'palette.success.dark')}
      </Grid>
    </Box>
  );
}

Customization

You may override the default palette values by including a palette object as part of your theme. If any of the palette.primary, palette.secondary, palette.error, palette.warning, palette.info or palette.success palette color objects are provided, they will replace the default ones.

When only main is given, light, dark and contrastText are calculated from it. You can also provide them directly:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      palette = list(
        primary = list(main = "#FF5733"),
        secondary = list(main = "#E0C2FF", light = "#F5EBFF", contrastText = "#47008F")
      )
    ),
    Stack(
      direction = "row",
      spacing = 2,
      Button(variant = "contained", "Primary"),
      Button(variant = "contained", color = "secondary", "Secondary")
    )
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import PropTypes from 'prop-types';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import { unstable_capitalize as capitalize } from '@mui/utils';
import Typography from '@mui/material/Typography';

const theme = createTheme({
  palette: {
    primary: {
      main: '#FF5733',
      // light: will be calculated from palette.primary.main,
      // dark: will be calculated from palette.primary.main,
      // contrastText: will be calculated to contrast with palette.primary.main
    },
    secondary: {
      main: '#E0C2FF',
      light: '#F5EBFF',
      // dark: will be calculated from palette.secondary.main,
      contrastText: '#47008F',
    },
  },
});

function ColorShowcase({ color }) {
  return (
    <Stack sx={{ gap: 2, alignItems: 'center' }}>
      <Button variant="contained" color={color}>
        {capitalize(color)}
      </Button>
      <Stack direction="row" sx={{ gap: 1 }}>
        <Stack sx={{ alignItems: 'center' }}>
          <Typography variant="body2">light</Typography>
          <Box sx={{ bgcolor: `${color}.light`, width: 40, height: 20 }} />
        </Stack>
        <Stack sx={{ alignItems: 'center' }}>
          <Typography variant="body2">main</Typography>
          <Box sx={{ bgcolor: `${color}.main`, width: 40, height: 20 }} />
        </Stack>
        <Stack sx={{ alignItems: 'center' }}>
          <Typography variant="body2">dark</Typography>
          <Box sx={{ bgcolor: `${color}.dark`, width: 40, height: 20 }} />
        </Stack>
      </Stack>
    </Stack>
  );
}

ColorShowcase.propTypes = {
  color: PropTypes.oneOf(['primary', 'secondary']).isRequired,
};

export default function ManuallyProvidePaletteColor() {
  return (
    <ThemeProvider theme={theme}>
      <Stack direction="row" sx={{ gap: 8 }}>
        <ColorShowcase color="primary" />
        <ColorShowcase color="secondary" />
      </Stack>
    </ThemeProvider>
  );
}

contrastThreshold (3 by default) and tonalOffset (0.2 by default) control how contrastText, light and dark are computed:

ThemeProvider(theme = list(palette = list(contrastThreshold = 4.5, tonalOffset = 0.25)), ...)

Custom colors

You can add your own colors to the palette, and use them with the color prop of the components and in sx:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      palette = list(
        ochre = list(main = "#E3D026", light = "#E9DB5D", dark = "#A29415", contrastText = "#242105")
      )
    ),
    Stack(
      direction = "row",
      spacing = 2,
      sx = list(alignItems = "center"),
      Button(variant = "contained", color = "ochre", "Ochre"),
      Chip(color = "ochre", label = "Ochre chip"),
      Box(sx = list(bgcolor = "ochre.light", p = 1, borderRadius = 1), "ochre.light")
    )
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';

// Augment the palette to include an ochre color

// Update the Button's color options to include an ochre option

const theme = createTheme({
  palette: {
    ochre: {
      main: '#E3D026',
      light: '#E9DB5D',
      dark: '#A29415',
      contrastText: '#242105',
    },
  },
});

export default function ManuallyProvideCustomColor() {
  return (
    <ThemeProvider theme={theme}>
      <Stack sx={{ gap: 2, alignItems: 'center' }}>
        <Button variant="contained" color="ochre">
          Ochre
        </Button>
        <Stack direction="row" sx={{ gap: 1 }}>
          <Stack sx={{ alignItems: 'center' }}>
            <Typography variant="body2">light</Typography>
            <Box sx={{ bgcolor: 'ochre.light', width: 40, height: 20 }} />
          </Stack>
          <Stack sx={{ alignItems: 'center' }}>
            <Typography variant="body2">main</Typography>
            <Box sx={{ bgcolor: 'ochre.main', width: 40, height: 20 }} />
          </Stack>
          <Stack sx={{ alignItems: 'center' }}>
            <Typography variant="body2">dark</Typography>
            <Box sx={{ bgcolor: 'ochre.dark', width: 40, height: 20 }} />
          </Stack>
        </Stack>
      </Stack>
    </ThemeProvider>
  );
}

Dark mode

Material UI comes with two palette modes: light (the default) and dark. You can make the theme dark by setting mode = "dark":

muiMaterialPage(
  ThemeProvider(
    theme = list(palette = list(mode = "dark")),
    CssBaseline(),
    Paper(
      sx = list(p = 2),
      Stack(
        spacing = 2,
        Typography("This paper uses the dark palette."),
        Stack(direction = "row", spacing = 2, Button(variant = "contained", "Contained"), Button(variant = "outlined", "Outlined"))
      )
    )
  )
)
JS code
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import { styled, ThemeProvider, useTheme, createTheme } from '@mui/material/styles';

const Root = styled('div')(({ theme }) => ({
  backgroundColor: theme.palette.background.default,
  color: theme.palette.text.primary,
  padding: theme.spacing(2),
  borderRadius: 4,
  [theme.breakpoints.up('md')]: {
    padding: theme.spacing(3),
  },
}));

const Color = styled(Grid)(({ theme }) => ({
  display: 'flex',
  alignItems: 'center',
  '& div:first-of-type': {
    width: theme.spacing(5),
    height: theme.spacing(5),
    flexShrink: 0,
    marginRight: theme.spacing(1.5),
    borderRadius: theme.shape.borderRadius,
  },
}));

function Demo() {
  const theme = useTheme();

  const item = (color, name, expanded = false, border = false) => (
    <Color size={{ xs: 12, sm: 6, md: expanded ? 8 : 4 }}>
      <div
        style={{
          backgroundColor: color,
          border: border ? `1px solid ${theme.palette.divider}` : undefined,
        }}
      />
      <div>
        <Typography variant="body2">{name}</Typography>
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {color}
        </Typography>
      </div>
    </Color>
  );

  return (
    <Root>
      <Typography gutterBottom sx={{ mb: 1.5 }}>
        Typography
      </Typography>
      <Grid container spacing={1}>
        {item(theme.palette.text.primary, 'palette.text.primary')}
        {item(theme.palette.text.secondary, 'palette.text.secondary')}
        {item(theme.palette.text.disabled, 'palette.text.disabled')}
      </Grid>
      <Typography gutterBottom sx={{ mt: 4, mb: 1.5 }}>
        Buttons
      </Typography>
      <Grid container spacing={1}>
        {item(theme.palette.action.active, 'palette.action.active')}
        {item(theme.palette.action.hover, 'palette.action.hover')}
        {item(theme.palette.action.selected, 'palette.action.selected')}
        {item(theme.palette.action.disabled, 'palette.action.disabled')}
        {item(
          theme.palette.action.disabledBackground,
          'palette.action.disabledBackground',
          true,
        )}
      </Grid>
      <Typography gutterBottom sx={{ mt: 4, mb: 1.5 }}>
        Background
      </Typography>
      <Grid container spacing={1}>
        {item(
          theme.palette.background.default,
          'palette.background.default',
          false,
          true,
        )}
        {item(
          theme.palette.background.paper,
          'palette.background.paper',
          false,
          true,
        )}
      </Grid>
      <Typography gutterBottom sx={{ mt: 4, mb: 1.5 }}>
        Divider
      </Typography>
      <Grid container spacing={1}>
        {item(theme.palette.divider, 'palette.divider')}
      </Grid>
    </Root>
  );
}

const darkTheme = createTheme({
  palette: {
    // Switching the dark mode on is a single property value change.
    mode: 'dark',
  },
});

export default function DarkTheme() {
  return (
    <Box sx={{ width: '100%' }}>
      <ThemeProvider theme={darkTheme}>
        <Demo />
      </ThemeProvider>
    </Box>
  );
}

Adding CssBaseline() inside the ThemeProvider() applies the dark background to the whole page (above, the demo only changes its Paper()).

Toggling color mode

In React, the mode is kept in state and toggled with useColorScheme(). From R, build one theme per mode, and choose it:

library(reactRouter)

modeDemo <- function(mode) {
  ThemeProvider(
    theme = list(palette = list(mode = mode)),
    Box(
      sx = list(
        display = "flex", width = "100%", alignItems = "center", justifyContent = "center",
        bgcolor = "background.default", color = "text.primary", borderRadius = 1, p = 3
      ),
      paste(mode, "mode"),
      IconButton(
        sx = list(ml = 1),
        href = if (mode == "dark") "#/" else "#/mode/dark",
        color = "inherit",
        Icon(if (mode == "dark") "brightness_7" else "brightness_4")
      )
    )
  )
}

muiMaterialPage(
  useMaterialIconsFilled = TRUE,
  CssBaseline(),
  RouterProvider(
    router = createHashRouter(
      Route(path = "mode/dark", element = modeDemo("dark")),
      Route(path = "*", element = modeDemo("light"))
    )
  )
)
JS code
import Box from '@mui/material/Box';
import RadioGroup from '@mui/material/RadioGroup';
import Radio from '@mui/material/Radio';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormLabel from '@mui/material/FormLabel';
import { ThemeProvider, createTheme, useColorScheme } from '@mui/material/styles';

function MyApp() {
  const { mode, setMode } = useColorScheme();
  if (!mode) {
    return null;
  }
  return (
    <Box
      sx={{
        display: 'flex',
        width: '100%',
        alignItems: 'center',
        justifyContent: 'center',
        bgcolor: 'background.default',
        color: 'text.primary',
        borderRadius: 1,
        p: 3,
        minHeight: '56px',
      }}
    >
      <FormControl>
        <FormLabel id="demo-theme-toggle">Theme</FormLabel>
        <RadioGroup
          aria-labelledby="demo-theme-toggle"
          name="theme-toggle"
          row
          value={mode}
          onChange={(event) => setMode(event.target.value)}
        >
          <FormControlLabel value="system" control={<Radio />} label="System" />
          <FormControlLabel value="light" control={<Radio />} label="Light" />
          <FormControlLabel value="dark" control={<Radio />} label="Dark" />
        </RadioGroup>
      </FormControl>
    </Box>
  );
}

const theme = createTheme({
  colorSchemes: {
    dark: true,
  },
});

export default function ToggleColorMode() {
  return (
    <ThemeProvider theme={theme}>
      <MyApp />
    </ThemeProvider>
  );
}

Typography

The theme provides a set of type sizes that work well together, and also with the layout grid.

Font family

You can change the font family with the typography$fontFamily theme property. Load the font first, for example from Google Fonts with an htmltools::htmlDependency() (this is how muiMaterialPage(useFontRoboto = TRUE) loads Roboto):

lobster <- htmltools::htmlDependency(
  name = "font-lobster", version = "1.0.0", src = c(href = "https://fonts.googleapis.com"),
  head = '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lobster&display=swap">'
)

muiMaterialPage(
  CssBaseline(),
  lobster,
  ThemeProvider(
    theme = list(typography = list(fontFamily = "'Lobster', cursive")),
    Typography(variant = "h4", "A custom font family"),
    Button(variant = "outlined", "Also on buttons")
  )
)

Font size

Material UI uses rem units for the font size. The browser <html> element default font size is 16px, but browsers have an option to change this value, so rem units allow us to accommodate the user’s settings, resulting in a better accessibility support. Users change font size settings for all kinds of reasons, from poor eyesight to choosing optimum settings for devices that can be vastly different in size and viewing distance.

To change the font-size of Material UI you can provide a fontSize property. The default value is 14px.

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(typography = list(fontSize = 12)),
    Typography("body1 with a 12px base font size"),
    Button(variant = "contained", "Small button text")
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Typography from '@mui/material/Typography';

const theme = createTheme({
  typography: {
    // Tell MUI what the font-size on the html element is.
    htmlFontSize: 10,
  },
});

export default function FontSizeTheme() {
  return (
    <ThemeProvider theme={theme}>
      <Typography>body1</Typography>
    </ThemeProvider>
  );
}

Variants

The typography object comes with 13 variants by default. Each of these variants can be customized individually:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      typography = list(
        subtitle1 = list(fontSize = 12),
        body1 = list(fontWeight = 500),
        button = list(fontStyle = "italic")
      )
    ),
    Typography(variant = "subtitle1", "subtitle"),
    Typography("body1"),
    Button("Button")
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';

const theme = createTheme({
  typography: {
    subtitle1: {
      fontSize: 12,
    },
    body1: {
      fontWeight: 500,
    },
    button: {
      fontStyle: 'italic',
    },
  },
});

export default function TypographyVariants() {
  return (
    <div>
      <ThemeProvider theme={theme}>
        <Typography variant="subtitle1">subtitle</Typography>
        <Typography>body1</Typography>
        <Button>Button</Button>
      </ThemeProvider>
    </div>
  );
}

Adding & disabling variants

In addition to using the default typography variants, you can add custom ones. A new variant is a new key of typography, used with Typography(variant = ...). Set variantMapping to choose the HTML element it renders:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      typography = list(poster = list(fontSize = "4rem", color = "red")),
      components = list(MuiTypography = list(defaultProps = list(variantMapping = list(poster = "h1"))))
    ),
    Typography(variant = "poster", "poster")
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';

const theme = createTheme({
  typography: {
    poster: {
      fontSize: '4rem',
      color: 'indianred',
    },
    // Disable v3 variant
    h3: undefined,
  },
  components: {
    MuiTypography: {
      defaultProps: {
        variantMapping: {
          poster: 'h1', // map our new variant to render an <h1> by default
        },
      },
    },
  },
});

export default function TypographyCustomVariant() {
  return (
    <ThemeProvider theme={theme}>
      <Box sx={{ '& > *': { display: 'block' } }}>
        {/* @ts-ignore */}
        <Typography variant="poster">poster</Typography>
        <Typography variant="h3">h3</Typography>
      </Box>
    </ThemeProvider>
  );
}

Themed components

You can customize a component’s styles, default props, and more by using its component key inside the theme. This helps to achieve styling consistency across your application.

Theme default props

Every Material UI component has default values for each of its props. To change these default values, use the defaultProps key exposed in the theme’s components key:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      components = list(
        # Name of the component
        MuiButtonBase = list(
          defaultProps = list(
            # The props to change the default for.
            disableRipple = TRUE # No more ripple, on the whole application 💣!
          )
        )
      )
    ),
    Button("This button has disabled ripples.")
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Button from '@mui/material/Button';

const theme = createTheme({
  components: {
    // Name of the component ⚛️
    MuiButtonBase: {
      defaultProps: {
        // The default props to change
        disableRipple: true, // No more ripple, on the whole application 💣!
      },
    },
  },
});

export default function DefaultProps() {
  return (
    <ThemeProvider theme={theme}>
      <Button>This button has disabled ripples.</Button>
    </ThemeProvider>
  );
}

Theme style overrides

The theme’s styleOverrides key makes it possible to change the default styles of any Material UI component.

styleOverrides requires a slot name as a key (use root to target the outer-most element) and an object with CSS properties as a value. Nested CSS selectors are also supported as values.

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      components = list(
        # Name of the component
        MuiButton = list(
          styleOverrides = list(
            # Name of the slot
            root = list(
              # Some CSS
              fontSize = "1rem"
            )
          )
        )
      )
    ),
    Button("font-size: 1rem")
  )
)
JS code
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';

const theme = createTheme({
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          fontSize: '1rem',
        },
      },
    },
  },
});

export default function GlobalThemeOverride() {
  return (
    <ThemeProvider theme={theme}>
      <Button>font-size: 1rem</Button>
    </ThemeProvider>
  );
}

Variants

Most components have variants (for example Button has text, outlined and contained). The variants key of styleOverrides adds styles for a combination of props, and new variants can be added the same way:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(
      components = list(
        MuiButton = list(
          styleOverrides = list(
            root = list(
              variants = list(
                list(props = list(variant = "dashed"), style = list(textTransform = "none", border = "2px dashed grey")),
                list(props = list(variant = "dashed", color = "secondary"), style = list(border = "4px dashed #9c27b0"))
              )
            )
          )
        )
      )
    ),
    Stack(
      direction = "row",
      spacing = 2,
      Button(variant = "dashed", sx = list(m = 2), "Dashed"),
      Button(variant = "dashed", color = "secondary", sx = list(m = 2), "Secondary")
    )
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Button from '@mui/material/Button';

const customTheme = createTheme({
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          variants: [
            {
              props: { variant: 'dashed' },
              style: ({ theme }) => ({
                textTransform: 'none',
                border: `2px dashed ${theme.palette.primary.main}`,
                color: theme.palette.primary.main,
              }),
            },
            {
              props: { variant: 'dashed', color: 'secondary' },
              style: ({ theme }) => ({
                border: `2px dashed ${theme.palette.secondary.main}`,
                color: theme.palette.secondary.main,
              }),
            },
            {
              props: { variant: 'dashed', size: 'large' },
              style: {
                borderWidth: 4,
              },
            },
            {
              props: { variant: 'dashed', color: 'secondary', size: 'large' },
              style: {
                fontSize: 18,
              },
            },
          ],
        },
      },
    },
  },
});

export default function GlobalThemeVariants() {
  return (
    <ThemeProvider theme={customTheme}>
      <Button variant="dashed" sx={{ m: 1 }}>
        Dashed
      </Button>
      <Button variant="dashed" color="secondary" sx={{ m: 1 }}>
        Secondary
      </Button>
      <Button variant="dashed" size="large" sx={{ m: 1 }}>
        Large
      </Button>
      <Button variant="dashed" color="secondary" size="large" sx={{ m: 1 }}>
        Secondary large
      </Button>
    </ThemeProvider>
  );
}

Theme variables

Another way to override the look of all component instances is to adjust the theme configuration variables:

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = list(typography = list(button = list(fontSize = "1rem"))),
    Button("font-size: 1rem")
  )
)
JS code
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';

const theme = createTheme({
  typography: {
    button: {
      fontSize: '1rem',
    },
  },
});

export default function ThemeVariables() {
  return (
    <ThemeProvider theme={theme}>
      <Button>font-size: 1rem</Button>
    </ThemeProvider>
  );
}

Nesting the theme

You can nest multiple theme providers. In muiMaterial, a nested ThemeProvider() builds a complete theme from its own options: it does not inherit the options of the outer one. To extend a theme, build the inner options from the outer ones with modifyList():

brand <- list(palette = list(primary = list(main = "#6a1b9a"), secondary = list(main = "#ffb300")))
darkBrand <- modifyList(brand, list(palette = list(mode = "dark")))

muiMaterialPage(
  CssBaseline(),
  ThemeProvider(
    theme = brand,
    Stack(
      spacing = 2,
      Button(variant = "contained", "Outer theme"),
      ThemeProvider(
        theme = darkBrand,
        Paper(sx = list(p = 2), Button(variant = "contained", color = "secondary", "Inner theme: same brand colors, dark mode"))
      )
    )
  )
)
JS code
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Checkbox from '@mui/material/Checkbox';
import { green, orange } from '@mui/material/colors';

const outerTheme = createTheme({
  palette: {
    secondary: {
      main: orange[500],
    },
  },
});

export default function ThemeNestingExtend() {
  return (
    <ThemeProvider theme={outerTheme}>
      <Checkbox defaultChecked color="secondary" />
      <ThemeProvider
        theme={(theme) =>
          createTheme({
            ...theme,
            palette: {
              ...theme.palette,
              primary: {
                main: green[500],
              },
            },
          })
        }
      >
        <Checkbox defaultChecked />
        <Checkbox defaultChecked color="secondary" />
      </ThemeProvider>
    </ThemeProvider>
  );
}

Theming in Shiny apps

Define the theme once as an R list and reuse it. To switch themes at runtime (for example a dark mode toggle), render the ThemeProvider() with renderReact():

library(shiny)
library(muiMaterial)

brand <- list(palette = list(primary = list(main = "#6a1b9a")))

ui <- muiMaterialPage(reactOutput("app"))

server <- function(input, output, session) {
  output$app <- renderReact({
    ThemeProvider(
      theme = modifyList(brand, list(palette = list(mode = if (isTRUE(input$dark)) "dark" else "light"))),
      CssBaseline(),
      Box(
        sx = list(p = 2),
        FormControlLabel(control = Switch.shinyInput("dark", value = isTRUE(input$dark)), label = "Dark mode"),
        Button(variant = "contained", "A themed button")
      )
    )
  })
}

shinyApp(ui, server)

Run a complete example with muiMaterialExample("ThemeProvider").