Icons in a React project
Tags: react, gatsby, svg, icons
Published: 2020-04-28 18:52:09

When I'm working on a project that needs icons, I always reach for [Nucleo](https://nucleoapp.com/) icons. (No, they're not paying me. But they _are_ really good.) Both their native and web apps allow for easy exporting of the SVG, but the native app can also export in JSX, which is perfect for my blog which runs on Gatsby, which itself runs on React.

This website's component structure is pretty straightforward: all the icons are located in `src/components/icons`, each icon having its own file. For example, the "left arrow" icon is named `arrow-left.js`. Being JSX, all the icons have a similar structure. For example purposes, I'm going to use one of their free icons. It is a paid product, after all.

``` jsx
import React from 'react';

function Zoom(props) {
	const title = props.title || "zoom";

	return (
		<svg height="24" width="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
			<title>{title}</title>
			<g fill="currentColor">
				<path d="M23.061,20.939l-5.733-5.733a9.028,9.028,0,1,0-2.122,2.122l5.733,5.733ZM3,10a7,7,0,1,1,7,7A7.008,7.008,0,0,1,3,10Z" fill="currentColor"/>
			</g>
		</svg>
	);
};

export default Zoom;
```

This is fine to start with, but my icon use within the website is often alongside text, like this:

``` jsx
<button type="button">
	<Zoom />
	Search
</button>
```

In this use case, the icon's default title will result in a screen reader interpreting the button text as "zoom search," which would be confusing. So I removed the `const title` line and modified the title element to include a ternary operator:

``` jsx
{!!props.title &&
	<title>{props.title}</title>
}
```

This allows the title to only be written if it's included in the component's use, like this:

``` jsx
<Zoom title="search" />
```

In my above example, though, I also don't want the icon visible to screen readers at all. So I added the `aria-hidden` property, which also looks at the title:

``` jsx
<svg aria-hidden={!props.title}>
```

All of this is well and good for each icon, but I have to make these changes all over again whenever I add a new icon. (Okay, it's not _that_ often, but it's still tedious.) We can improve this and make it a little more DRY, right? Right?

With that in mind, I created a new file: `/src/components/icons.js`. Within this file, a single function returns the SVG icon framework:

``` jsx
const icon = (path, className, title) => {
	return (
		<svg className={`icon ${className}`} aria-hidden={!title} height="24" width="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
			{!!title &&
				<title>{title}</title>
			}
			<g fill="currentColor">
				{path}
			</g>
		</svg>
	)
}
```

It uses the default `.icon` class (which my CSS framework styles with default height, color, etc.) and accepts additional classes. It also uses the `title` argument to determine ARIA visibility and the title element. Most importantly, it also accepts a custom `path` which, of course, determine's the icon's appearance.

The file exports all the icons used by my website. To do that, it returns the `icon` function call:

``` jsx
export const Zoom = (props) => {
	return icon(paths.zoom, `icon--zoom${props.className ? ` ${props.className}` : ''}`, props.title)
}
```

You'll notice that the `path` is not defined here. Instead, I'm calling `paths.zoom` -- the constant `paths` is defined at the top of the file:

``` jsx
const paths = {
	zoom: <path d="M23.061,20.939l-5.733-5.733a9.028,9.028,0,1,0-2.122,2.122l5.733,5.733ZM3,10a7,7,0,1,1,7,7A7.008,7.008,0,0,1,3,10Z" fill="currentColor"/>,
}
```

Every time I add a new icon, I copy its `path` and add it to this object and add a new export. It seems to me to be a little less work than adding a new file and making changes to it, but... I don't know. I'm open to suggestions.

The other added benefit to managing icons this way is importing them. With the icons all existing in separate files, including multiple icons looked something like this:

``` jsx
import { Heart } from "@icons/heart"
import { Clock } from "@icons/clock"
import { OpenExternal } from "@icons/open-external"
```

Now, importing multiple icons can be done on a single line:

``` jsx
import { Heart, Clock, OpenExternal } from "@icons"
```

I guess it's all about preference. There are many like it, as they say, and this one is mine. And speaking of preferences, I'm also simplifying my imports with the [`gatsby-plugin-alias-imports`](https://www.gatsbyjs.org/packages/gatsby-plugin-alias-imports/) plugin. I like it. 👍

_This post was originally published on [Bold Oak Design](https://boldoak.design/blog/icons-in-a-react-project/)._