A Step-by-Step Guide to Developing a Flexible Icon Component.

On 21 / 04 / 2023 / by Robin Vanvoorden

This blog post provides a step-by-step guide on how to optimize SVG files for web use, including exporting SVGs, removing unnecessary attributes, optimizing paths, and using SVG sprites. It also includes code snippets and visual examples to help illustrate the process.

In web development, icons are an essential component of any user interface. They help communicate information quickly and effectively, and can often serve as a visual cue to a particular function or feature. When building an application or website, it's important to have access to a wide range of icons that can be easily customised to fit your design. In this blog post, we'll explore how to create a customisable icon component in React and Next.js by exporting SVGs and transforming them into React components that can be easily styled and manipulated.

Having a custom icon component is essential when you want to easily create new components on the fly.

Robin / Developer

By adding some extra options to your component you can easily tweak the colors and sizing of your icon, but still have the benefit of a default styling layer inside the component itself. This gives you a better consistency overall and thanks to the use of enums you can even have some extra quality of life features like autocomplete inside you IDE.

With this knowledge, you'll be able to create a library of custom icons that can be used across multiple projects and applications, saving time and effort in the long run. So let's dive in and learn how to create a customisable icon component in React and Next.js!

There will be a step-by-step guide in the following sections. However if you want to see the component in action and pick it apart for yourself, there's a code sandbox link at the bottom of the page.

1. Exporting your SVG’s

First we’ll need all our .svg files. In our example we’re exporting them from figma and storing them our public folder.

However not all svg files are the same. When you export them might not have a viewBox attribute or have some clippingmask added to it.

<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.707 2.29298C10.5184 2.11082 10.2658 2.01003 10.0036 2.01231C9.7414 2.01458 9.49059 2.11975 9.30518 2.30516C9.11977 2.49057 9.0146 2.74138 9.01233 3.00358C9.01005 3.26578 9.11084 3.51838 9.293 3.70698L12.586 6.99998H1C0.734784 6.99998 0.48043 7.10534 0.292893 7.29287C0.105357 7.48041 0 7.73476 0 7.99998C0 8.2652 0.105357 8.51955 0.292893 8.70709C0.48043 8.89462 0.734784 8.99998 1 8.99998H12.586L9.293 12.293C9.19749 12.3852 9.12131 12.4956 9.0689 12.6176C9.01649 12.7396 8.9889 12.8708 8.98775 13.0036C8.9866 13.1364 9.0119 13.268 9.06218 13.3909C9.11246 13.5138 9.18671 13.6255 9.28061 13.7194C9.3745 13.8133 9.48615 13.8875 9.60905 13.9378C9.73194 13.9881 9.86362 14.0134 9.9964 14.0122C10.1292 14.0111 10.2604 13.9835 10.3824 13.9311C10.5044 13.8787 10.6148 13.8025 10.707 13.707L15.707 8.70698C15.8945 8.51945 15.9998 8.26514 15.9998 7.99998C15.9998 7.73482 15.8945 7.48051 15.707 7.29298L10.707 2.29298Z" fill="#162C51"/>
</svg>

2. Controlling size

Instead of the width=”16” and height=”16” properties we’re going to add viewBox="0 0 16 16”. This will make sure the proportions will stay the same, but not constrain our icon’s size to a hardcoded width or height.

<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.707 2.29298C10.5184 2.11082 10.2658 2.01003 10.0036 2.01231C9.7414 2.01458 9.49059 2.11975 9.30518 2.30516C9.11977 2.49057 9.0146 2.74138 9.01233 3.00358C9.01005 3.26578 9.11084 3.51838 9.293 3.70698L12.586 6.99998H1C0.734784 6.99998 0.48043 7.10534 0.292893 7.29287C0.105357 7.48041 0 7.73476 0 7.99998C0 8.2652 0.105357 8.51955 0.292893 8.70709C0.48043 8.89462 0.734784 8.99998 1 8.99998H12.586L9.293 12.293C9.19749 12.3852 9.12131 12.4956 9.0689 12.6176C9.01649 12.7396 8.9889 12.8708 8.98775 13.0036C8.9866 13.1364 9.0119 13.268 9.06218 13.3909C9.11246 13.5138 9.18671 13.6255 9.28061 13.7194C9.3745 13.8133 9.48615 13.8875 9.60905 13.9378C9.73194 13.9881 9.86362 14.0134 9.9964 14.0122C10.1292 14.0111 10.2604 13.9835 10.3824 13.9311C10.5044 13.8787 10.6148 13.8025 10.707 13.707L15.707 8.70698C15.8945 8.51945 15.9998 8.26514 15.9998 7.99998C15.9998 7.73482 15.8945 7.48051 15.707 7.29298L10.707 2.29298Z" fill="#162C51"/>
</svg>

3. Controlling the color

We also want to control our icon’s color from outside our component. If you’re not catching on yet, we’re going to add a class to the svg file later to control size or color. So the inline styling tags of fill or stroke shouldn’t be hardcoded either.You might think deleting them and just adding fill: [YOUR COLOR] and stroke:[YOUR COLOR] would work and you would be correct.

This could have some issues however, since not all svg files are equal and some might even multiple colors or multiple paths, rect’s, … with strokes and fills with or without colors.

What you want to be able to do is tell your file which tags should be able to take the color from your class and none of the other tags. To do this we’re using the color property in css and instead of deleting the original fill or stroke color tags we’re going to change them into ‘currentColor’. This way all you need to do is change the color of your class and all the changed property tags will also be that color.

We’re also setting the size inside a class.

Your svg file will look like this:

.icon {
  color: #162C51;
  display: inline-block;
  height: 16px;
  box-sizing: border-box;
  width: 16px;

  svg {
    display: block;
  }
}

Now we have a svg icon with customisable sizes and colors, but to make it a little easier to use we’re going to pour it into a nicely formatted component.

4. Setting up your icon component

For our component we want it to do a few things: change the icon of choice, add a class to the component. Other things like adding on onClick event can, of course, be added later on.

First we import all the svg files.

import React from "react";
import classNames from "classnames"
import styles from "./Icon.module.scss";

import ArrowUp from "@public/icons/ArrowUp.svg";
import ArrowDown from "@public/icons/ArrowDown.svg";
import ArrowLeft from "@public/icons/ArrowLeft.svg";
import ArrowRight from "@public/icons/ArrowRight.svg";

Then we’re going to add our react component and give it the options we need:

import React from "react";
import classNames from "classnames"
import styles from "./Icon.module.scss";

import ArrowUp from "@public/icons/ArrowUp.svg";
import ArrowDown from "@public/icons/ArrowDown.svg";
import ArrowLeft from "@public/icons/ArrowLeft.svg";
import ArrowRight from "@public/icons/ArrowRight.svg";

interface IconProps {
    className?: string;
}

export const Icon = ({ className, ...props }: IconProps) => {

return (
    <i className={classNames(styles.icon, "icon", className)} {...props}>
      <ArrowUp />
    </i>
  );
}

Now we have a component we can add to any other react component, but to easily see what icons we have in our library we’re going to use enums. Setting this up is a little bit of work, but it adds some great quality of life autocomplete features inside VS Code. This way you can use the component give it the proper enum as an iconType and that’s all you’ll have to think about.

import React from "react";
import classNames from "classnames"
import styles from "./Icon.module.scss";

import ArrowUp from "@public/icons/ArrowUp.svg";
import ArrowDown from "@public/icons/ArrowDown.svg";
import ArrowLeft from "@public/icons/ArrowLeft.svg";
import ArrowRight from "@public/icons/ArrowRight.svg";

export enum IconsEnum {
  ARROWUP = "arrowup",
	ARROWDOWN = "arrowdown",
	ARROWRIGHT = "arrowright",
	ARROWLEFT = "arrowleft",
}

interface IconProps {
  iconType: IconsEnum;
  className?: string;
}

export const Icon = ({ iconType = IconsEnum.ARROWRIGHT, className, ...props }: IconProps) => {

const generateIcon = (icon: IconsEnum) => {
switch (icon) {
	case IconsEnum.ARROWUP:
	        return <ArrowUp />;

	case IconsEnum.ARROWDOWN:
	        return <ArrowDown />;

	case IconsEnum.ARROWLEFT:
	        return <ArrowLeft />;

	case IconsEnum.ARROWRIGHT:
	        return <ArrowRight />;
	default:
	        return <ArrowRight />;
    }
  };

return (
    <i className={classNames(styles.icon, "icon", className)} {...props}>
      {generateIcon(iconType)}
    </i>
  );
}

The sandbox example

If you want to go through the details yourself or just want to see it in action, you can click the link to view the code sandbox example.