A few years ago I made the switch from iPhone to Android. I wanted to escape Apple's walled garden and start using technology that permitted me to modify and customize it to my liking, to make my own. I made a techy dashboard for my phone, with design help from my partner Helena Zhang. She designed around 40 custom, minimal icons to replace the common utility app icons and fit in with the hacker aesthetic.
Over time that seed grew, eventually becoming a complete set of icons for Android home screens, replacing icons for over 800 of the most common apps with clean, minimalistic, monochromatic glyphs. We released different colorways, took icon suggestions, got involved in the small but active community of phone customizers. Helena even wrote several articles on the subject of icon design and history. We were eating, breathing, and sleeping icons.
Eventually we realized that all this effort could serve a much larger audience of digital designers and engineers of all walks, not just the Android enthusiasts. We decided to create a library of general-purpose user interface icons for modern web, desktop, and mobile platforms — and Phosphor Icons was born.
I knew that its success would not come down to the design of the icons alone. We wanted to make an icon library that was as easy to learn as it was to use, covered the vast majority of platforms, and just worked. We needed to provide a top-tier developer experience to those using the icons. This meant not only supporting the most common frameworks, with packages for React, Vue.js, and vanilla JS, but also exposing an intuitive API and writing great documentation.
We wanted to make an icon library that was as easy to learn as it was to use, covered the vast majority of platforms, and just worked.
Taking advantage of React's Context API
and Vue's provide/inject
feature, I brought stateful local and global styling to the icon components. You could set the appearance of all icons in a subtree from one place, but unlike CSS it could be dynamic and manipulated programmatically.
import React from "react";
import { createRoot } from 'react-dom/client';
import { IconContext, Horse, Heart, Cube } from "phosphor-react";
const App = () => {
return (
<IconContext.Provider
value={{
color: "limegreen",
size: 32,
weight: "bold",
mirrored: false,
}}
>
<div>
<Horse /> {/* I'm lime-green, 32px, and bold! */}
<Heart /> {/* Me too! */}
<Cube /> {/* Me three :) */}
</div>
</IconContext.Provider>
);
};
createRoot(document.getElementById('root')!).render(<App />);
When working with large component libraries, it's important to factor out sources of human error. Mistakes crop up inevitably at scale, and with 9,072 icons and their corresponding implementations — packages in 5 javascript frameworks, a Figma plugin and library, and other things I'm forgetting — we had a lot of complexity to manage by hand. This led me to build custom internal tooling to support our efforts from the design process all the way through to production, including:
A small battery of unit and integration tests ensured that our automated approach wouldn't propagate subtle bugs throughout the system. And at the end of it all, we generated example apps to allow thorough inspection of every icon with the good ol' Mark I Eyeball™. This usually consisted of large contact-sheets with multiple instances of each icon in its many variants and sizes, arranged to visually highlight inconsistencies.
Aside from reducing carpal tunnel risks for my partner and myself, the automation and testing processes gave us a confidence we couldn't get doing all of this manually.
Naturally, we needed a fun and functional site to house the project. We wanted a searchable interface for the icon library that showcased each icon variant, along with code snippets for developers to quickly reference. And to show off a bit with sleek, snappy animations and strong visual demonstrations of the icons in use.
I took it as an opportunity to try out some new tools in the React ecosystem. TypeScript formed the foundation for less buggy code with more compile-time guarantees. I used Recoil, a new experimental state management library from Facebook. It is an absolute joy to work with, and eliminates hundreds of lines of Redux boilerplate. I implemented a smart client-size fuzzy search with help from the awesome Fuse.js library, making searching for icons much more forgiving.
Ultimately, we managed to pack a ton of ergonomic features into a simple and straightforward one-page site, and even received some accolades from sites like:
We continue to grow the library, using analytics and feedback from the community to add the most-needed icons first. We hope to expand to support more use-cases for developers and designers alike. A Flutter library, along with a number of other third-party ports, have since been added, and we have plans for more down the road.