React: Proper Icon Usage

Intro

There are many ways to use icons in React applications, however after trying out several methods in multiple applications I have landed on the following technique being my favorite. In this article we are going to be using icons from heroicons although the process would be the same for any icon that you can download in an SVG format.

Creating Icon Components

To start we want to turn each individual icon that we use into a functional component. Once we have a component for each icon we can simply reference that component instead of inlining the same SVG multiple times. The color of the icon is passed in as a prop, so that we can change the color of multiple icons at once without having to go into each component manually.

components/icons/Hamburger.js
import React from "react"

const Hamburger = ({ Color }) => {
  return (
    <>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        stroke={Color}      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth="2"
          d="M4 6h16M4 12h16M4 18h16"
        />
      </svg>
    </>
  )
}

export default Hamburger

Also note that we have placed our icon inside the folder components/icons for organizational purposes. This is an example of one icon. Repeat this process for every icon that you use.

Using Icon Components

Now let’s look at how these icons would be used in a menu. In this example we have a header component that uses a LandingMenu and LandingMenuItem component, which are both totally re-usable. Because we are passing the icons that are being used with these components as props, we can vastly cut down on the amount of repetitive code. This method and DRY and readable.

components/Header.js
import React, { Component } from "react"

/* Components */
import LandingMenu from "./LandingMenu"
import LandingMenuItem from "./LandingMenuItem"

/* Icons */
import ChartBar from "./icons/ChartBar"
import Beaker from "./icons/Beaker"
import Support from "./icons/Support"
import Template from "./icons/Template"
import ChevronDown from "./icons/ChevronDown"

/* Styles */
import "../../styles/variables.css"

class LandingHeader extends Component {
  /* Icon Colors */
  color = "var(--icon-color)"
  render() {
    return (
      <>
        <LandingMenu Title="Features" Icon={<ChevronDown />}>
          <LandingMenuItem
            Icon={<ChartBar Color={this.color} />}
            Title="Analysis"
            Description="We provide an amazing service for your customers."
          />
          <LandingMenuItem
            Icon={<Beaker Color={this.color} />}
            Title="Product Tracking"
            Description="Our widgets are the best in the business."
          />
          <LandingMenuItem
            Icon={<Support Color={this.color} />}
            Title="Support"
            Description="There is only a 50% chance we will put you on hold for an hour and hang up on you."
          />
          <LandingMenuItem
            Icon={<Template Color={this.color} />}
            Title="Item Title"
            Description="This is a solid description of our widget."
          />
        </LandingMenu>
      </>
    )
  }
}

export default Header

Also note that we simply specified the color as a variable, which then references a CSS variable. In this way we are still specifying the color of the icons in our CSS files, which satisfies the separation of concerns principle. We can change the color of all the icons at once easily inside our CSS. We could also just as easily use multiple colors by using multiple variables here, such as icon-color-primary and icon-color-secondary etc etc.

Alternate Icon Sets

Because not all icon SVG’s are formatted the same way that heroicons are, you sometimes have to tinker with the SVG code to get this system to work for you. Here are instructions for alternate icon sets.

To start with make sure that you download an SVG formatting extension for your text editor, that will make this process much easier.

IBM Icons

One of my personal favorite icon sets, and the set that is in use on this website. The IBM icon set is free, massive and very clean. However you can’t just copy and paste the SVG inline like you would with some others.

One solution to this if you are making a React application is to use the IBM Icons NPM Package which is a fine solution. I don’t like to include libraries if I don’t have to, especially if i’m not confident it’s going to get tree shaken. It is not a large amount of work to use the icons I need individually. Here is how the IBM icons are formatted directly off the website

<svg id="icon" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
    <defs>        <style>            .cls-1 {                fill: none;            }        </style>    </defs>    <title>3D-software</title>
    <path
        d="M21.49,13.1151l-9-5a1,1,0,0,0-1,0l-9,5A1.0078,1.0078,0,0,0,2,14v9.9951a1,1,0,0,0,.52.87l9,5A1.0045,1.0045,0,0,0,12,30a1.0559,1.0559,0,0,0,.49-.1349l9-5A.9923.9923,0,0,0,22,24V14A1.0079,1.0079,0,0,0,21.49,13.1151ZM11,27.2951l-7-3.89v-7.72l7,3.89Zm1-9.45L5.06,14,12,10.1351l6.94,3.86Zm8,5.56-7,3.89v-7.72l7-3.89Z" />
    <polygon points="30 6 26 6 26 2 24 2 24 6 20 6 20 8 24 8 24 12 26 12 26 8 30 8 30 6" />
    <rect
        id="_Transparent_Rectangle_"        data-name="&lt;Transparent Rectangle&gt;"        class="cls-1"        width="32" height="32"
    />
</svg>

To clean this up we need to delete the whole defs section. Up in the SVG section we add a fill which is where we give it our color prop, and then down in rect we delete the id + data-name and change the class to “ibm-icon” or whatever you prefer. So our final IBM icon component will look like this.

components/icons/Dashboard.js
import React from "react"

const Dashboard = ({ Color }) => {
  return (
    <>
      <svg
        id="icon"
        xmlns="http://www.w3.org/2000/svg"
        width="32"
        height="32"
        viewBox="0 0 32 32"
        fill={Color}      >
        <title>dashboard</title>
        <rect x="24" y="21" width="2" height="5" />
        <rect x="20" y="16" width="2" height="10" />
        <path d="M11,26a5.0059,5.0059,0,0,1-5-5H8a3,3,0,1,0,3-3V16a5,5,0,0,1,0,10Z" />
        <path d="M28,2H4A2.002,2.002,0,0,0,2,4V28a2.0023,2.0023,0,0,0,2,2H28a2.0027,2.0027,0,0,0,2-2V4A2.0023,2.0023,0,0,0,28,2Zm0,9H14V4H28ZM12,4v7H4V4ZM4,28V13H28.0007l.0013,15Z" />
        <rect class="ibm-icon" width="32" height="32" />      </svg>
    </>
  )
}

export default Dashboard

Lastly you need to make sure that you define the ibm-icon CSS class with fill: none or you will just get a black square where the logo should be.

.ibm-icon {
  fill: none;
}