Skip to contents
This page is an adaptation of the related MUI Material UI documentation page.

Icons

Guidance and suggestions for using icons with muiMaterial.

In React, Material UI provides icons in three ways: SVG icons exported as React components by the @mui/icons-material package, the SvgIcon component for custom SVG icons, and the Icon component for font icons. The @mui/icons-material package (over 2,000 components) is not bundled with muiMaterial, to keep the package small. From R, you use:

  1. The Icon() component with the Material Icons font, loaded by muiMaterialPage(). This is the simplest way to use Material icons.
  2. The SvgIcon() component, a wrapper for custom SVG icons.
  3. Font Awesome icons with shiny::icon().

Icon (Font icons)

The Icon component will display an icon from any icon font that supports ligatures. As a prerequisite, you must include one, such as the Material Icons font. To use an icon simply wrap the icon name (font ligature) with the Icon component, for example: Icon("star").

The icon names are the snake_case names of the Material icons (for example delete_forever, add_a_photo).

By default, an Icon will inherit the current text color. Optionally, you can set the icon color using one of the theme color properties: primary, secondary, action, error & disabled.

Font Material Icons

Icon will by default set the correct base class name for the Material Icons font (filled variant). All you need to do is load the font with muiMaterialPage(useMaterialIconsFilled = TRUE). It works in Shiny apps, R Markdown and Quarto documents.

muiMaterialPage(
  useMaterialIconsFilled = TRUE,
  CssBaseline(),
  Stack(
    direction = "row",
    spacing = 3,
    Icon("add_circle"),
    Icon(color = "primary", "add_circle"),
    Icon(sx = list(color = "#4caf50"), "add_circle"), # green[500]
    Icon(fontSize = "small", "add_circle"),
    Icon(sx = list(fontSize = 30), "add_circle")
  )
)
JS code
import Stack from '@mui/material/Stack';
import { green } from '@mui/material/colors';
import Icon from '@mui/material/Icon';

export default function Icons() {
  return (
    <Stack direction="row" spacing={3}>
      <Icon>add_circle</Icon>
      <Icon color="primary">add_circle</Icon>
      <Icon sx={{ color: green[500] }}>add_circle</Icon>
      <Icon fontSize="small">add_circle</Icon>
      <Icon sx={{ fontSize: 30 }}>add_circle</Icon>
    </Stack>
  );
}

Icon themes

Each Material icon also has a “theme”: Filled (default), Outlined, Rounded, and Two-tone. muiMaterialPage() loads each font with its own argument, and the baseClassName prop of Icon() selects the font:

Theme muiMaterialPage() argument baseClassName
Filled useMaterialIconsFilled = TRUE "material-icons" (default)
Outlined useMaterialIconsOutlined = TRUE "material-icons-outlined"
Rounded useMaterialIconsRounded = TRUE "material-icons-round"
Two-tone useMaterialIconsTwoTones = TRUE "material-icons-two-tone"
themes <- c(Filled = "material-icons", Outlined = "material-icons-outlined", Rounded = "material-icons-round", "Two Tone" = "material-icons-two-tone")

muiMaterialPage(
  useMaterialIconsFilled = TRUE,
  useMaterialIconsOutlined = TRUE,
  useMaterialIconsRounded = TRUE,
  useMaterialIconsTwoTones = TRUE,
  CssBaseline(),
  Box(
    sx = list(flexGrow = 1),
    Grid(
      container = TRUE,
      sx = list(color = "text.primary"),
      lapply(names(themes), function(theme) {
        tagList(
          Grid(size = 4, Typography(theme)),
          Grid(size = 8, Icon(baseClassName = themes[[theme]], "delete"), Icon(baseClassName = themes[[theme]], "delete_forever"))
        )
      })
    )
  )
)
JS code
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import DeleteIcon from '@mui/icons-material/Delete';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import DeleteTwoToneIcon from '@mui/icons-material/DeleteTwoTone';
import DeleteSharpIcon from '@mui/icons-material/DeleteSharp';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import DeleteForeverOutlinedIcon from '@mui/icons-material/DeleteForeverOutlined';
import DeleteForeverRoundedIcon from '@mui/icons-material/DeleteForeverRounded';
import DeleteForeverTwoToneIcon from '@mui/icons-material/DeleteForeverTwoTone';
import DeleteForeverSharpIcon from '@mui/icons-material/DeleteForeverSharp';
import ThreeDRotationIcon from '@mui/icons-material/ThreeDRotation';
import FourKIcon from '@mui/icons-material/FourK';
import ThreeSixtyIcon from '@mui/icons-material/ThreeSixty';

export default function SvgMaterialIcons() {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <Grid container sx={{ color: 'text.primary' }}>
        <Grid size={4}>
          <Typography>Filled</Typography>
        </Grid>
        <Grid size={8}>
          <DeleteIcon />
          <DeleteForeverIcon />
        </Grid>
        <Grid size={4}>
          <Typography>Outlined</Typography>
        </Grid>
        <Grid size={8}>
          <DeleteOutlinedIcon />
          <DeleteForeverOutlinedIcon />
        </Grid>
        <Grid size={4}>
          <Typography>Rounded</Typography>
        </Grid>
        <Grid size={8}>
          <DeleteRoundedIcon />
          <DeleteForeverRoundedIcon />
        </Grid>
        <Grid size={4}>
          <Typography>Two Tone</Typography>
        </Grid>
        <Grid size={8}>
          <DeleteTwoToneIcon />
          <DeleteForeverTwoToneIcon />
        </Grid>
        <Grid size={4}>
          <Typography>Sharp</Typography>
        </Grid>
        <Grid size={8}>
          <DeleteSharpIcon />
          <DeleteForeverSharpIcon />
        </Grid>
        <Grid size={4}>
          <Typography>Edge-cases</Typography>
        </Grid>
        <Grid size={8}>
          <ThreeDRotationIcon />
          <FourKIcon />
          <ThreeSixtyIcon />
        </Grid>
      </Grid>
    </Box>
  );
}
Global base class name

Modifying the baseClassName prop for each component usage is repetitive. You can change the default prop globally with the theme:

muiMaterialPage(
  useMaterialIconsTwoTones = TRUE,
  CssBaseline(),
  ThemeProvider(
    theme = list(
      components = list(
        # Replace the `material-icons` default value.
        MuiIcon = list(defaultProps = list(baseClassName = "material-icons-two-tone"))
      )
    ),
    Icon("add_circle")
  )
)
JS code
import { useTheme } from '@mui/material/styles';
import Icon from '@mui/material/Icon';

const useIsDarkMode = () => {
  const theme = useTheme();
  return theme.palette.mode === 'dark';
};

export default function TwoToneIcons() {
  const isDarkMode = useIsDarkMode();

  return (
    <Icon
      sx={[isDarkMode && { filter: 'invert(1)' }]}
      baseClassName="material-icons-two-tone"
    >
      add_circle
    </Icon>
  );
}

SvgIcon

If you need a custom SVG icon, you can use the SvgIcon wrapper. This component extends the native <svg> element:

  • It comes with built-in accessibility.
  • SVG elements should be scaled for a 24x24px viewport so that the resulting icon can be used as is, or included as a child for other Material UI components that use icons. This can be customized with the viewBox attribute. To inherit the viewBox value from the original image, the inheritViewBox prop can be used.
  • By default, the component inherits the current color. Optionally, you can apply one of the theme colors using the color prop.
  • It supports <svg> element as a child so you can copy and paste your SVG directly to SvgIcon component.

SVG elements are written with htmltools::tag() (or tags$svg()), with the React attribute names (strokeWidth, strokeLinecap, …):

muiMaterialPage(
  CssBaseline(),
  SvgIcon(
    # credit: cog icon from https://heroicons.com
    tags$svg(
      fill = "none", viewBox = "0 0 24 24", strokeWidth = 1.5, stroke = "currentColor",
      tag("path", list(
        strokeLinecap = "round",
        strokeLinejoin = "round",
        d = "M4.5 12a7.5 7.5 0 0015 0m-15 0a7.5 7.5 0 1115 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077l1.41-.513m14.095-5.13l1.41-.513M5.106 17.785l1.15-.964m11.49-9.642l1.149-.964M7.501 19.795l.75-1.3m7.5-12.99l.75-1.3m-6.063 16.658l.26-1.477m2.605-14.772l.26-1.477m0 17.726l-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205L12 12m6.894 5.785l-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864l-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495"
      ))
    )
  )
)
JS code
import SvgIcon from '@mui/material/SvgIcon';

export default function SvgIconChildren() {
  return (
    <SvgIcon>
      {/* credit: cog icon from https://heroicons.com */}
      <svg fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          d="M4.5 12a7.5 7.5 0 0015 0m-15 0a7.5 7.5 0 1115 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077l1.41-.513m14.095-5.13l1.41-.513M5.106 17.785l1.15-.964m11.49-9.642l1.149-.964M7.501 19.795l.75-1.3m7.5-12.99l.75-1.3m-6.063 16.658l.26-1.477m2.605-14.772l.26-1.477m0 17.726l-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205L12 12m6.894 5.785l-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864l-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495"
        />
      </svg>
    </SvgIcon>
  );
}

Color

A small R function plays the role of a React icon component:

HomeIcon <- function(...) SvgIcon(..., tag("path", list(d = "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z")))

muiMaterialPage(
  CssBaseline(),
  Stack(
    direction = "row",
    spacing = 3,
    HomeIcon(),
    HomeIcon(color = "primary"),
    HomeIcon(color = "secondary"),
    HomeIcon(color = "success"),
    HomeIcon(color = "action"),
    HomeIcon(color = "disabled"),
    HomeIcon(sx = list(color = "#e91e63")) # pink[500]
  )
)
JS code
import Stack from '@mui/material/Stack';
import { pink } from '@mui/material/colors';
import SvgIcon from '@mui/material/SvgIcon';

function HomeIcon(props) {
  return (
    <SvgIcon {...props}>
      <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
    </SvgIcon>
  );
}

export default function SvgIconsColor() {
  return (
    <Stack direction="row" spacing={3}>
      <HomeIcon />
      <HomeIcon color="primary" />
      <HomeIcon color="secondary" />
      <HomeIcon color="success" />
      <HomeIcon color="action" />
      <HomeIcon color="disabled" />
      <HomeIcon sx={{ color: pink[500] }} />
    </Stack>
  );
}

Size

muiMaterialPage(
  CssBaseline(),
  Stack(
    direction = "row",
    spacing = 3,
    sx = list(alignItems = "flex-end"),
    HomeIcon(fontSize = "small"),
    HomeIcon(),
    HomeIcon(fontSize = "large"),
    HomeIcon(sx = list(fontSize = 40))
  )
)
JS code
import Stack from '@mui/material/Stack';
import SvgIcon from '@mui/material/SvgIcon';

function HomeIcon(props) {
  return (
    <SvgIcon {...props}>
      <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
    </SvgIcon>
  );
}

export default function SvgIconsSize() {
  return (
    <Stack direction="row" spacing={3} sx={{ alignItems: 'flex-end' }}>
      <HomeIcon fontSize="small" />
      <HomeIcon />
      <HomeIcon fontSize="large" />
      <HomeIcon sx={{ fontSize: 40 }} />
    </Stack>
  );
}

Other libraries

MDI

materialdesignicons.com provides over 2,000 icons. For the wanted icon, copy the SVG path they provide, and use it as the child of the SvgIcon() component, as HomeIcon() above. The same works for any Material icon: copy the SVG path from Google Fonts and wrap it in SvgIcon().

Font Awesome

Font Awesome icons are available with shiny::icon(), which also loads the icon font. They can be used wherever an icon is expected:

muiMaterialPage(
  CssBaseline(),
  Stack(
    direction = "row",
    spacing = 4,
    sx = list(alignItems = "center"),
    shiny::icon("plus"),
    Box(sx = list(color = "primary.main"), shiny::icon("circle-plus")),
    Box(sx = list(color = "#4caf50", fontSize = 30), shiny::icon("circle-plus")),
    IconButton(`aria-label` = "github", shiny::icon("github")),
    Button(variant = "contained", startIcon = shiny::icon("download"), "Download")
  )
)
JS code
import * as React from 'react';
import { loadCSS } from 'fg-loadcss';
import Stack from '@mui/material/Stack';
import { green } from '@mui/material/colors';
import Icon from '@mui/material/Icon';

export default function FontAwesomeIcon() {
  React.useEffect(() => {
    const node = loadCSS(
      'https://use.fontawesome.com/releases/v5.14.0/css/all.css',
      // Inject before JSS
      document.querySelector('#font-awesome-css') || document.head.firstChild,
    );

    return () => {
      node.parentNode.removeChild(node);
    };
  }, []);

  return (
    <Stack direction="row" spacing={4} sx={{ alignItems: 'flex-end' }}>
      <Icon baseClassName="fas" className="fa-plus-circle" />
      <Icon baseClassName="fas" className="fa-plus-circle" color="primary" />
      <Icon
        baseClassName="fas"
        className="fa-plus-circle"
        sx={{ color: green[500] }}
      />
      <Icon baseClassName="fas" className="fa-plus-circle" fontSize="small" />
      <Icon baseClassName="fas" className="fa-plus-circle" sx={{ fontSize: 30 }} />
    </Stack>
  );
}

Note that the Font Awesome icons weren’t designed like the Material Icons (compare the two previous demos). The fa icons are cropped to use all the space available.

Font vs. SVGs: Which approach to use?

Both approaches work fine, however, there are some subtle differences, especially in terms of performance and rendering quality. In React, SVG icons are preferred as they allow code splitting, support more icons, and render faster and better. In R, the Material Icons font is the most practical way to use Material icons, and SvgIcon() is the way to add a custom icon.

Accessibility

Icons can convey all sorts of meaningful information, so it’s important to ensure they are accessible where appropriate. There are two use cases you’ll want to consider:

  • Decorative icons that are only being used for visual or branding reinforcement. If they were removed from the page, users would still understand and be able to use your page.
  • Semantic icons are ones that you’re using to convey meaning, rather than just pure decoration. This includes icons without text next to them that are used as interactive controls: buttons, form elements, toggles, etc.

Decorative icons

If your icons are purely decorative, you’re already done! The aria-hidden=true attribute is added so that your icons are properly accessible (invisible).

Semantic icons

Semantic SVG icons

You should include the titleAccess prop with a meaningful value. The role="img" attribute and the <title> element are added so that your icons are correctly accessible.

In the case of focusable interactive elements, for example when used with an icon button, you can use the aria-label prop:

IconButton(
  `aria-label` = "delete",
  SvgIcon(tag("path", list(d = "M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z")))
)
Semantic font icons

You need to provide a text alternative that is only visible to assistive technologies.

visuallyHidden <- list(
  border = 0, clip = "rect(0 0 0 0)", height = "1px", margin = "-1px", overflow = "hidden",
  padding = 0, position = "absolute", whiteSpace = "nowrap", width = "1px"
)
tagList(
  Icon("add_circle"),
  Box(component = "span", sx = visuallyHidden, "Create a user")
)