// blog/design/
Back to Blog
Design · Published August 6, 2026 · 7 min read · By Toine ·

Update note: Rewritten from experience; icon counts and Heroicons styles updated, SVG Optimizer and SVG to PNG linked in place of two decorative links

SVG Icon Libraries in 2026: Pick One, Match the Stroke, and Never Mix Two

SVG Icon Libraries in 2026: Pick One, Match the Stroke, and Never Mix Two

An icon library is a choice you make once and then see on every screen for years, which is why the wrong one is so annoying. Icons that fight the design, a bundle that carries a thousand icons to show twelve, or one missing icon that drags in a second library and a second stroke width. In chain testing with several suppliers I can usually tell which screen came from which team by the icons alone, and if I can, users can.

The good news is that the field has settled. Five open-source libraries cover nearly every project, all free for commercial use, all with packages for the main frameworks, and the differences are in the drawing style, the count, the weight per icon and how cleanly they tree-shake. This post compares them so you pick once, and the rule I would put at the top: pick one library, use its stroke width everywhere, and treat the first icon from a second library as a defect.

* * *

The five libraries, and what each is for

Lucide (a community fork of Feather, since 2020): - About 1,600 icons, 24 x 24 grid, 2 px rounded stroke - Roughly 400 bytes an icon as a React component - Packages for React, Vue, Svelte, Angular, Solid and Preact, plus plain SVG - ISC licence - Fits: SaaS dashboards, admin panels, developer tools; the default choice when nothing argues for another

Heroicons (Tailwind Labs): - About 300 icons in four styles: outline and solid at 24 px, mini at 20 px, micro at 16 px - Around 500 bytes an icon - Official packages for React and Vue only - MIT - Fits: Tailwind projects, where they were drawn to match; a small set drawn carefully

Phosphor: - About 1,500 icons in six weights: thin, light, regular, bold, fill and duotone - 300 to 600 bytes depending on the weight - React, Vue, Svelte, Flutter, Elm and web components - MIT - Fits: interfaces that need weight for hierarchy, a bold icon on the primary action and a light one everywhere else, without a second library

Tabler Icons: - Close to 6,000 icons, outline and filled, 24 x 24 with an adjustable stroke - About 350 bytes an icon - React, Vue, Svelte, Angular, Solid, Preact, React Native and a web font - MIT - Fits: anything with a long tail of domain-specific icons, where the question is not style but whether the icon exists at all

Bootstrap Icons: - About 2,000 icons, drawn for Bootstrap and usable without it - Around 400 bytes an icon; shipped as SVG files, a sprite and a font - MIT - Fits: Bootstrap projects and server-rendered sites that want a sprite and no build step

Two more worth knowing about: Material Symbols if the product lives in Google's visual language, and Radix Icons if you are already on Radix UI and want the small 15 px set that matches it.

Grid of SVG icons in different styles
Grid of SVG icons in different styles
* * *

Import by name, and the library's total size stops mattering

Every library here tree-shakes. Import twenty icons by name and the bundle carries twenty icons, so the total count is irrelevant to load time and the per-icon weight is what you pay.

`javascript // Ships three icons import { Search, Menu, X } from 'lucide-react';

// Ships all of them import * as Icons from 'lucide-react'; `

The second form is how a project ends up with 300 KB of icons for a search box, and it usually arrives through a dynamic icon picker that maps a string to a component. If you need that, build the map from the icons you actually use, not from the namespace.

Per icon in a React bundle: Lucide about 400 bytes, Heroicons about 500, Phosphor 300 to 600 by weight, Tabler about 350. Fifty icons is 20 to 30 KB before compression and a fraction of that after, which is smaller than most single images on the page.

For a server-rendered or static site, an SVG sprite is still the cheapest option: one file, one request, wherever the icon appears. Inline the two or three icons in the first screen so they render with the HTML and do not shift the layout when the sprite arrives.

A custom icon drawn to match should go through the SVG Optimizer before it enters the repository, because editors export ids, comments and metadata that the library icons do not carry, and the difference shows up as a 4 KB icon sitting next to 400-byte ones. When an icon has to leave the web, an email signature or a social image, the SVG to PNG converter renders it at the pixel size the target needs.

Key takeaway

Every library here tree-shakes.

* * *

Which one for which stack

React with Tailwind: Heroicons if the 300 cover you, Lucide if they do not. Same grid, similar stroke, and a Heroicons project that outgrows the set moves to Lucide with little visible change.

Vue: Lucide or Phosphor, both with typed Vue packages. Phosphor's weights are the reason to choose it.

Svelte: Lucide or Tabler, both with dedicated packages. Tabler's size means you will not need a second library for the odd domain icon.

Angular: Lucide or Tabler again; Bootstrap Icons when the project already runs on Bootstrap.

Server-rendered or static HTML: any of them as a sprite. Bootstrap Icons and Tabler ship one ready-made.

React Native: Phosphor and Tabler have native packages; the others go through react-native-svg as components.

Design handover: all five publish Figma libraries. Check that the designer's file and the developer's package are the same library and the same major version, because an icon renamed between versions is a small, irritating defect that recurs on every screen it appears on.

The browser tab icon is a separate job: the Favicon Generator turns the logo or a single library icon into the set of sizes browsers and phones ask for.

Designer choosing icons for web application
Designer choosing icons for web application
* * *

Adding a custom icon so nobody can tell

The library will be missing something, usually a product-specific concept, and the choice is a second library or a custom icon. Draw the custom one.

  • Same grid, almost always 24 x 24, with the same viewBox
  • Same stroke width and the same corner radius on the stroke joins
  • Same optical size: icons should look the same size, which is not the same as filling the same pixels; a circle and a square of equal width do not look equal
  • Same attribute structure on export, fill="none", stroke="currentColor", so it takes colour from the text like the rest

If a second library is unavoidable, force its icons to the primary library's stroke width with stroke-width in CSS and make sure the viewBox and display size match. A 20 px icon next to a 24 px one reads as a mistake even to people who cannot say why.

Icon fonts are the older approach and SVG has won: an SVG icon can be labelled for a screen reader, styled per icon, and tree-shaken, and it does not render as a missing-glyph box when the font fails to load. If a project still uses a font, the switch is worth a sprint.

Label what means something, hide what does not:

`jsx // or, when there is visible text next to it `

An icon-only button without a label is the accessibility defect I log most often in acceptance testing, and the fix is one attribute.

Key takeaway

The library will be missing something, usually a product-specific concept, and the choice is a second library or a custom icon.

* * *

FAQ

Which library has the most icons?

Tabler, at close to 6,000. Count is the wrong question, though. Search the library for the ten icons your product cannot do without before you look at the total; a 1,600-icon library that has all ten beats a 6,000-icon one that has nine.

Are they free for commercial use?

Yes. Lucide is ISC, the other four are MIT, none needs attribution. Read the licence file once anyway, and again after a major version.

How do I change icon colour and size?

The icons use currentColor, so the CSS color of the parent sets the icon colour. Size is width and height, or the size prop on the framework components, which all five provide.

Self-host or CDN?

Self-host. A CDN is an external dependency and a point of failure for something that weighs 20 to 50 KB in total, and self-hosting gives you the version pinning that stops an icon changing shape under you.