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:
- The
Icon()component with the Material Icons font, loaded bymuiMaterialPage(). This is the simplest way to use Material icons. - The
SvgIcon()component, a wrapper for custom SVG icons. -
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
viewBoxattribute. To inherit theviewBoxvalue from the original image, theinheritViewBoxprop can be used. - By default, the component inherits the current color. Optionally,
you can apply one of the theme colors using the
colorprop. - It supports
<svg>element as a child so you can copy and paste your SVG directly toSvgIconcomponent.
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")))
)