DEV Community

Cover image for Custom Icon components in MUI v5
Hosein Pouyanmehr
Hosein Pouyanmehr

Posted on • Updated on

Custom Icon components in MUI v5

Table of contents

What is this post about?

In this short tutorial, you'll learn to make your own MUI icon component which will behave as same as MUI icons. As you may know, icons provided in the @mui/icons-material package can easily understand MUI theming and, they can simply communicate with other MUI components. Thanks to the MUI SvgIcon component you can easily create your icon component that looks like MUI icons.

Prerequisites

I think you already have an environment perfectly set up, but I want to point this that having @mui/icons-material isn't necessary. So as a minimum, you need a react app as well as @mui/material.

Steps

I will write both TypeScript and JavaScript approaches. So, for instance, if you write your code in TS you can skip JS parts or vice versa. Also, I'll use @emotion as it's the default style library used in MUI v5.

Step One: Imports

Create a file with your desired name. I'm going to name it "Mopeim" and import React at the top.

1 import * as React from 'react';
Enter fullscreen mode Exit fullscreen mode

JS

Then we need to import the SvgIcon component and styled utility from @mui/matarial, So the code will be like this:

1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, styled } from '@mui/material';
Enter fullscreen mode Exit fullscreen mode

TS

In TypeScript, we also need to import the SvgIconProps type to create our new component properly.

1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from '@mui/material';
Enter fullscreen mode Exit fullscreen mode

The reason that I renamed SvgIcon to MuiSvgIcon is that in the next step we're going to create a new styled SvgIcon and we'll name that new component SvgIcon. You'll get it better in the next step.

Step Two: Create a styled SvgIcon component

At this step, we'll create a SvgIcon with our custom styles. Each path may need several CSS like fill or stroke etc. This SvgIcon in HTML will become a <svg></svg> tag with our styles.

The general look of our component and the styled function will be like this.

JS

const SvgIcon = styled(component, options)((props)=>(styles))
Enter fullscreen mode Exit fullscreen mode

TS

const SvgIcon = styled(component, options)<PropsType>((props)=>(styles))
Enter fullscreen mode Exit fullscreen mode

In both TS and JS approaches, first, we call the styled function and then pass a component to that. This component can be one of MUI components or even simple HTML tags like an a or a button etc. Here we want to create a svg tag, and we want to make it in the MUI way. So we pass the SvgIcon component as the first prop to the styled function.

For options, you should pass an object containing all options you want. I'm not going to explain all the available styled options as you can read about them here in the MUI documents. Here, I use name and shouldForwardProp options to set a name for our new SvgIcon Component and also shouldForwardProp to say which property should or shouldn't forward to the styles. You also can ignore these options as they're optional. MUI docs explain these two options like this:

  • options.shouldForwardProp ((prop: string) => bool [optional]): Indicates whether the prop should be forwarded to the Component.

  • options.name (string [optional]): The key used under theme.components for specifying styleOverrides and variants. Also used for generating the label.

This is how my icon looks:

Mopeim Logo

I want to name it "MopeimIcon" and I also want to avoid that to have a fill property. So let's add this logic to our code.

JS

...
3
4 const SvgIcon = styled(MuiSvgIcon, {
5   name: 'MopeimIcon',
6   shouldForwardProp: (prop) => prop !== 'fill',
7 })(() => ({
8   fill: 'none',
9   stroke: 'currentColor',
10  strokeLinecap: 'round',
11  strokeLinejoin:  'round',
12  strokeWidth:  '2.25px',
13 }));
Enter fullscreen mode Exit fullscreen mode

TS

...
3
4 const SvgIcon = styled(MuiSvgIcon, {
5   name: 'MopeimIcon',
6   shouldForwardProp: (prop) => prop !== 'fill',
7 })<SvgIconProps>(() => ({
8   fill: 'none',
9   stroke: 'currentColor',
10  strokeLinecap: 'round',
11  strokeLinejoin:  'round',
12  strokeWidth:  '2.25px',
13 }));
Enter fullscreen mode Exit fullscreen mode

Note: On line 6, when we want to define some logic for the shouldForwardProp we have to wrap the style prop in quotes. So this is NOT true:

...
6   shouldForwardProp: (prop) => prop !== fill, //Cannot find name 'fill'.
...
Enter fullscreen mode Exit fullscreen mode

If you need to use some of the props in your styling, you can pass them like this:

...
7 })<SvgIconProps>(({theme, anotherProp}) => ({
8   fill: theme.palette.primary.main,
9   borderRadius: theme.shape.borderRadius,
10  anotherStyle: anotherProp,
...
Enter fullscreen mode Exit fullscreen mode

Just make sure that the prop exists as a SvgIcon prop.

We can also use the defaultProps property on SvgIcon to set some defaults for our svg. So:

...
14
15 SvgIcon.defaultProps = {
16  viewBox: '0 0 24 24',
17  focusable: 'false',
18  'aria-hidden': 'true',
19  };
Enter fullscreen mode Exit fullscreen mode

In above props:

  • "viewBox" means the points "seen" in this SVG drawing area. 4 values separated by white space or commas. (min x, min y, width, height) Definition is from w3schools.
  • Using "focusable" and setting it to "false" make it unfocusable which is pretty self-explanatory. By the way, It means won't get focused when you press the tab key on your keyboard.
  • Adding aria-hidden="true" to an element removes that element and all of its children from the accessibility tree. Read more about this attribute here

Step Three: Create the component

The final step is to create our Icon component. We'll create a functional component and then we use the SvgIcon that we've modified before, and a path.
If you've designed your icon with tools such as Adobe Illustrator, export it as SVG and then extract the path and the styles from it. Otherwise, If you want to find the path of a free SVG icon, you can inspect it by your browser dev tools. My icon path is:

M15,19.16V15.07a4.27,4.27,0,0,0,6,0h0a4.27,4.27,0,0,0,0-6h0a4.27,4.27,0,0,0-6,0l-3,3-3,3a4.27,4.27,0,0,1-6,0h0a4.27,4.27,0,0,1,0-6h0A4.27,4.27,0,0,1,9,9
Enter fullscreen mode Exit fullscreen mode

JS

...
20
21 const Mopeim = (props) => {
22  return (
23      <SvgIcon {...props}>
24          <path d="M15,19.16V15.07a4.27,4.27,0,0,0,6,0h0a4.27,4.27,0,0,0,0-6h0a4.27,4.27,0,0,0-6,0l-3,3-3,3a4.27,4.27,0,0,1-6,0h0a4.27,4.27,0,0,1,0-6h0A4.27,4.27,0,0,1,9,9" />
25      </SvgIcon>
26  );
27 };
28
29 export default Mopeim;
30
Enter fullscreen mode Exit fullscreen mode

TS

...
20
21 const Mopeim: React.FunctionComponent<SvgIconProps> = (props) => {
22  return (
23      <SvgIcon {...props}>
24          <path d="M15,19.16V15.07a4.27,4.27,0,0,0,6,0h0a4.27,4.27,0,0,0,0-6h0a4.27,4.27,0,0,0-6,0l-3,3-3,3a4.27,4.27,0,0,1-6,0h0a4.27,4.27,0,0,1,0-6h0A4.27,4.27,0,0,1,9,9" />
25      </SvgIcon>
26  );
27 };
28
29 export default Mopeim;
30
Enter fullscreen mode Exit fullscreen mode

Final Code

The final code looks like this:

JS

1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, styled } from '@mui/material';
3
4 const SvgIcon = styled(MuiSvgIcon, {
5   name: 'MopeimIcon',
6   shouldForwardProp: (prop) => prop !== 'fill',
7 })(() => ({
8   fill: 'none',
9   stroke: 'currentColor',
10  strokeLinecap: 'round',
11  strokeLinejoin:  'round',
12  strokeWidth:  '2.25px',
13 }));
14
15 SvgIcon.defaultProps = {
16  viewBox: '0 0 24 24',
17  focusable: 'false',
18  'aria-hidden': 'true',
19  };
20
21 const Mopeim = (props) => {
22  return (
23      <SvgIcon {...props}>
24          <path d="M15,19.16V15.07a4.27,4.27,0,0,0,6,0h0a4.27,4.27,0,0,0,0-6h0a4.27,4.27,0,0,0-6,0l-3,3-3,3a4.27,4.27,0,0,1-6,0h0a4.27,4.27,0,0,1,0-6h0A4.27,4.27,0,0,1,9,9" />
25      </SvgIcon>
26  );
27 };
28
29 export default Mopeim;
30
Enter fullscreen mode Exit fullscreen mode

TS

1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from '@mui/material';
3
4 const SvgIcon = styled(MuiSvgIcon, {
5   name: 'MopeimIcon',
6   shouldForwardProp: (prop) => prop !== 'fill',
7 })<SvgIconProps>(() => ({
8   fill: 'none',
9   stroke: 'currentColor',
10  strokeLinecap: 'round',
11  strokeLinejoin:  'round',
12  strokeWidth:  '2.25px',
13 }));
14
15 SvgIcon.defaultProps = {
16  viewBox: '0 0 24 24',
17  focusable: 'false',
18  'aria-hidden': 'true',
19  };
20
21 const Mopeim: React.FunctionComponent<SvgIconProps> = (props) => {
22  return (
23      <SvgIcon {...props}>
24          <path d="M15,19.16V15.07a4.27,4.27,0,0,0,6,0h0a4.27,4.27,0,0,0,0-6h0a4.27,4.27,0,0,0-6,0l-3,3-3,3a4.27,4.27,0,0,1-6,0h0a4.27,4.27,0,0,1,0-6h0A4.27,4.27,0,0,1,9,9" />
25      </SvgIcon>
26  );
27 };
28
29 export default Mopeim;
30
Enter fullscreen mode Exit fullscreen mode

A banner of become a backer

Hi! I'm Hosein Pouyanmehr. I enjoy sharing what I learn and what I find interesting. Let's connect on LinkedIn.

See my code interests on GitHub.

Top comments (0)