Icon drift is one of the most predictable problems in product design, and one of the least discussed. It happens the same way in every organisation: nobody makes a bad decision, and the result is bad anyway.
Developer A needs a settings icon and picks settings. Developer B, three months later on a different screen, searches and picks adjustments because it looked better in context. Developer C imports a component library that ships its own icons. None of them did anything wrong. The product now has three settings icons, and users have learned none of them.
What actually fixes this
A one-page icon inventory: concept, icon name, where it is used. A rule that adding a new icon requires updating the inventory. That is it. Everything below is detail on how to do those two things well, but the inventory alone prevents most of the damage.
The inventory, which is the whole thing
An icon inventory is a table with four columns. It can live in a markdown file in your repo, a page in your design system, or a spreadsheet. Format matters far less than existing.
| Concept | Icon name | Used in | Notes |
|---|---|---|---|
| Settings | settings | Nav, account menu | Never use adjustments |
| Delete | trash-2 | Row actions, detail header | Always paired with confirm |
| Search | search | Global header, filters | |
| External link | arrow-up-right | All outbound links | 12px, after the text |
The value is in the first column. Listing icons is useless; listing concepts is what stops duplication, because the next person searches for the concept and finds the decision already made.
The "never use" note in the third row is doing more work than it looks. Recording the rejected alternative is what prevents someone re-litigating the choice in six months, which is the most common way inventories decay.
Rule one: one library
Pick one UI icon set and take everything from it. The comparison page covers choosing; the point here is that the decision must be recorded and enforced rather than assumed.
Two legitimate exceptions:
- Brand logos. These identify real companies and are not part of your UI icon language. Mixing them in is expected. See brand logos.
- Emoji and reactions, where the whole point is expressive colour.
The failure mode to watch is not a designer deliberately importing a second library. It is a component library, a charting package or an admin template arriving with its own icons attached. Audit your dependencies, not just your design files.
Naming: semantic or literal
Two approaches, and the mistake is mixing them.
Literal names describe the picture: trash, gear, magnifier. This is how icon libraries name things, because a library does not know what you will use an icon for.
Semantic names describe the purpose: delete, settings, search. This is how a design system should name things, because it survives the underlying icon changing.
The practical pattern is a thin alias layer: semantic names in your code, mapped to library names in one place.
// icons.ts
export { Trash2 as DeleteIcon,
Settings as SettingsIcon,
Search as SearchIcon,
ArrowUpRight as ExternalIcon } from 'lucide-react';
Now changing which icon represents deletion is a one-line change, and no component in your codebase knows or cares which library you use. This also makes migrating icon sets a mapping exercise rather than a codebase-wide find and replace, which is the difference between a half-day job and a fortnight.
Choosing a size scale
Three sizes. Not five, not "whatever fits".
| Token | Size | Used for |
|---|---|---|
icon-sm | 16px | Inline with body text, table rows, dense lists |
icon-md | 20px | Buttons, form fields, most UI. The default |
icon-lg | 24px | Navigation, page headers, primary actions |
Add a fourth only if you have illustrative icons in empty states or feature grids, typically 40 or 48px, and treat it as a separate category rather than an extension of the scale.
Two things people miss. Stroke weight should not be constant across the scale: a 2px stroke that looks right at 24px looks heavy at 16px and thin at 48px. And tap target size is independent of icon size: a 16px icon in a toolbar still needs at least a 44 by 44 pixel hit area. The icon is not the button. Detail in icon sizes.
Defining states once
Define your icon states as tokens and never set an icon colour ad hoc again.
:root {
--icon-default: #6B7280;
--icon-hover: #111827;
--icon-active: #FF5A36;
--icon-disabled: #D1D5DB;
--icon-danger: #DC2626;
--icon-success: #059669;
}
Because icons use currentColor, applying these is setting color, which means dark mode is a matter of redefining the variables and nothing else. See changing icon colour.
Also decide once how selected states are expressed: filled versus outline, colour change, or both. Whatever you choose, apply it everywhere. Inconsistent selected states are one of the fastest ways to make an interface feel unreliable.
Icons as design tokens
Once you have semantic names, sizes and state colours defined, icons stop being assets and become tokens, the same as your type scale or spacing scale. That has one important consequence: designers and developers use the same vocabulary.
A design review comment becomes "use DeleteIcon at icon-sm with --icon-danger" rather than "make the bin smaller and red". That precision is most of what a design system buys you, and it is available for icons far more cheaply than for most other parts of a system.
Who is allowed to add an icon
The governance question, and the honest answer is that a light rule beats a heavy process.
What does not work: a design lead approving every icon. It becomes a bottleneck, people route around it, and the inventory rots.
What works: anyone may add an icon, but the pull request must update the inventory. It is a thirty second addition and it forces the two questions that matter: does a concept for this already exist, and is this icon from our library?
Add a lightweight check for the third failure mode, since it is the one people forget: removing the last use of an icon should remove it from the inventory too. Otherwise the inventory grows monotonically and stops reflecting reality, at which point people stop trusting it.
Auditing an existing product
If you already have drift, a half-day audit fixes most of it.
- Find every icon. Grep for icon imports, SVG elements and icon class names. Include dependencies, which is where surprises live.
- Group by concept, not by file. This is where the three settings icons become visible.
- Pick one per concept. Where two are genuinely different concepts, name both clearly. Where they are not, choose and record the rejected one.
- Identify foreign libraries. Anything not from your chosen set gets replaced or explicitly justified.
- Write the inventory as you go. The audit produces it as a by-product; do not treat it as a separate task afterwards, because it will not happen.
- Fix in one pass. Icon consistency changes are low risk and high visibility, which makes them unusually easy to ship as a single change.
The audit is worth doing once. The inventory is what stops you needing to do it again.