37
Custom Icon components in MUI v5
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.
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
.
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.
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';
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';
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';
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.
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.
const SvgIcon = styled(component, options)((props)=>(styles))
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 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 theprop
should be forwarded to theComponent
.
options.name
(string [optional]): The key used undertheme.components
for specifyingstyleOverrides
andvariants
. Also used for generating thelabel
.
This is how my icon looks:
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.
...
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 }));
...
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:
...
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:
...
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:
...
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.
- 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
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
...
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
...
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
The final code looks like this:
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
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
37