Custom Icon components in MUI v5
Tags: mui, react, typescript, javascript
Published: 2021-12-29 11:10:56

## Table of contents
- [What is this post about?](#about)
- [Prerequisites](#prerequisites)
- [Step One: Imports](#step-one)
- [Step Two: Create a styled SvgIcon component](#step-two)
- [Step Three: Create the component](#step-three)
- [Final code](#final-code)

## <a name='#about'></a>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`](https://www.npmjs.com/package/@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.

## <a name='prerequisites'></a> 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.
### <a name='step-one'></a> Step One: Imports
Create a file with your desired name. I'm going to name it "Mopeim" and import React at the top.
```javascript
1 import * as React from 'react';
```
#### JS
Then we need to import the `SvgIcon` component and `styled` utility from `@mui/matarial`, So the code will be like this:
```javascript
1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, styled } from '@mui/material';
```
#### TS
In TypeScript, we also need to import the `SvgIconProps` type to create our new component properly.
```typescript
1 import * as React from 'react';
2 import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from '@mui/material';
```
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.

### <a name='step-two'></a> 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
```javascript
const SvgIcon = styled(component, options)((props)=>(styles))
```
#### TS
```typescript
const SvgIcon = styled(component, options)<PropsType>((props)=>(styles))
```
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](https://mui.com/system/styled/#styled-component-options-styles-component) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i0f1bykywcjmw3cxixm9.jpg)

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
```javascript
...
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 }));
```
#### TS
```typescript
...
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 }));
```

**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:
```javascript
...
6	shouldForwardProp: (prop) => prop !== fill, //Cannot find name 'fill'.
...
```
If you need to use some of the props in your styling, you can pass them like this:
```javascript
...
7 })<SvgIconProps>(({theme, anotherProp}) => ({
8	fill: theme.palette.primary.main,
9	borderRadius: theme.shape.borderRadius,
10	anotherStyle: anotherProp,
...
```
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:
```javascript
...
14
15 SvgIcon.defaultProps = {
16	viewBox: '0 0 24 24',
17	focusable: 'false',
18	'aria-hidden': 'true',
19	};
```
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](https://www.w3schools.com/).

- 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](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute)

### <a name='step-three'></a> 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
```

#### JS
```javascript
...
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
```
#### TS
```javascript
...
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
```
### <a name='final-code'></a> Final Code
The final code looks like this:
#### JS
```javascript
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
```
#### TS
```typescript
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
```


---

[![A banner of become a backer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fnb2hbuk8abtl1amqxhi.jpg)](https://hpouyanmehr.com/backers#donate)

Hi! I'm **Hosein Pouyanmehr**. I enjoy sharing what I learn and what I find interesting. Let's connect on [LinkedIn](https://www.linkedin.com/in/hpouyanmehr/).

See my code interests on [GitHub](https://github.com/HPouyanmehr).