CodeWorlds
Back to collections
Guide17 min readCodeWorlds Team

Iconify, one interface to many icon sets

Iconify exposes 236 icon sets through one component. Which packages are dead, how to stop calling a third party API, and what the licences really say.

Iconify, one interface to many icon sets

Iconify is a layer between your application and other people's icon sets. Instead of installing a separate package for every set, you hand the component a name in prefix:name format and the icon data arrives either from a public API or from a local JSON file. Release 2.2.519 of @iconify/json, dated 22 August 2026, contains 236 sets and 334,616 icons.

What Iconify actually ships

The project has three layers that are easy to confuse, because all of them carry the same name.

The first is the data format. An icon set is stored as a single IconifyJSON object with the fields prefix, info, lastModified, icons, width and height. The icons field holds bare <svg> element contents with no wrapper, and colours in monochrome icons are replaced with currentColor. Changing an icon's colour therefore comes down to changing the text colour in CSS.

The second is the data set itself, the iconify/icon-sets repository published as the @iconify/json package. It is refreshed several times a week, which the numbering shows: 2.2.517 shipped on 16 August 2026, 2.2.518 on the eighteenth and 2.2.519 on the twenty-second.

The third is the components and tooling: @iconify/react, @iconify/vue, @iconify/svelte, the iconify-icon web component, the unplugin-icons build plugin, the Tailwind plugins and the @iconify/utils library for manipulating data.

The layers are independent. You can take the format alone and read data with your own code, take the data alone and generate SVG files from it, or take a component and never touch the data. That explains why the packages are released separately and why some of them sit still.

Which packages are alive and which are frozen

This is the first thing to check, because documentation and tutorials from a few years ago point at a package you should not pick today.

The @iconify/iconify package sits at version 3.1.1, published on 22 June 2023, and nothing has shipped since. This is not abandonment but replacement, and the npm registry says so directly. The deprecated field in that version's metadata carries a sentence stating that the package is no longer maintained and that you should move to the modern iconify-icon web component. So if you find a build configuration entry that drops @iconify/iconify onto the page as a script, that is a leftover from the 2023 approach.

The rest of the family is healthy. The table below collects the state as of 22 August 2026, read from the npm registry.

PackageVersionRelease dateNotes
@iconify/iconify3.1.12023-06-22marked unmaintained, replaced by iconify-icon
iconify-icon3.0.22025-10-25web component, successor to the above
@iconify/react6.0.22025-09-15peer dependency react >=16
@iconify/vue5.0.12026-05-06peer dependency vue >=3.0.0
@iconify/svelte5.2.22026-06-11peer dependency svelte >5.0.0
@iconify/utils3.1.42026-07-05functions for working with the data
@iconify/json2.2.5192026-08-22data for every set
@iconify/tools5.0.122026-05-21building your own sets
unplugin-icons23.0.12026-01-14separate repository, the unplugin project
@iconify/types2.0.02022-09-08types only, stable for four years

It pays to check the dependency ranges inside the family separately, because mismatches do happen there. In this case there are none. Every component declares @iconify/types in the ^2.0.0 range and the current version of that package is 2.0.0. @iconify/tools 5.0.12 requires @iconify/utils in the ^3.1.3 range against a current 3.1.4, and unplugin-icons 23.0.1 requires ^3.1.0. Both conditions hold.

The 2022 date on @iconify/types does not signal neglect. That package contains nothing but TypeScript declarations for the data format, and the format has not changed. The Tailwind plugins are a different story: @iconify/tailwind 1.2.0 dates from 7 December 2024 and targets the third version of Tailwind CSS, while @iconify/tailwind4 1.2.3 from 5 March 2026 targets the fourth. These are two different packages, not two versions of one.

The answer to what you should pick in a new project in 2026 is therefore: the component for your framework or iconify-icon, data from @iconify/json or from single-set packages, and unplugin-icons for the build. Do not install @iconify/iconify.

Two component modes: API and offline

This is the most important technical decision with this tool, and the React package shows it most clearly, because it has two separate entry points written into its exports field.

The default import from @iconify/react is API mode. A component that receives an icon name as a string checks its cache and, if the data is missing, sends an HTTP request to https://api.iconify.design. That entry point exports the network functions: loadIcon, loadIcons, addAPIProvider, iconLoaded, setCustomIconLoader and the _api object.

The second entry point, @iconify/react/offline, exports only four things: Icon, InlineIcon, addCollection and addIcon. There is no network code in it at all. An icon you have not added beforehand through one of those two functions simply will not render. The signatures differ too: in API mode addCollection(data, provider?: string) returns a boolean, while in offline mode addCollection(data, prefix?: string | boolean) returns nothing.

This is the default mode, the one that loads data from someone else's server.

Code
TypeScript
// API mode: every newly displayed icon is an HTTP request
import { Icon } from '@iconify/react'

export function SaveButton() {
  return (
    <button type="button">
      <Icon icon="mdi:content-save" width={20} height={20} />
      Save
    </button>
  )
}

And this is the move to offline. You change the import path and register the data yourself.

Code
TypeScript
// offline mode: no network traffic, data inside the application bundle
import { Icon, addCollection } from '@iconify/react/offline'
import iconsSubset from './icons/subset.json'

addCollection(iconsSubset)

export function SaveButton() {
  return (
    <button type="button">
      <Icon icon="mdi:content-save" width={20} height={20} />
      Save
    </button>
  )
}

You have to produce subset.json yourself, because pulling in the whole mdi set would add 7,447 icons to the bundle. The getIcons function from @iconify/utils cuts out a subset, and lookupCollection from @iconify/json finds the source file.

TSscripts/build-icons.ts
TypeScript
// scripts/build-icons.ts, run before the application build
import { writeFile } from 'node:fs/promises'
import { lookupCollection } from '@iconify/json'
import { getIcons } from '@iconify/utils'

const mdi = await lookupCollection('mdi')
const subset = getIcons(mdi, ['content-save', 'delete-outline', 'pencil'])

if (!subset) {
  throw new Error('none of the requested icons were found')
}

await writeFile('./src/icons/subset.json', JSON.stringify(subset))

The same pair of modes exists in the other components. @iconify/vue 5.0.1 has an ./offline entry in its exports field. @iconify/svelte 5.2.2 exposes ./dist/OfflineIcon.svelte and ./dist/offline-functions alongside the networked variants.

The arguments for API mode are honest ones: you build nothing, the user downloads only the icons they will actually see, and the component has redundancy built in. The API documentation describes it precisely: if the main host does not answer within 0.75 seconds, the component tries the backup addresses https://api.simplesvg.com and https://api.unisvg.com, each of which points at half the servers.

The arguments against are equally concrete. Every page view by a new user means a request to a server you do not control, carrying their IP address and a Referer header. In a project under a privacy audit that is a separate item to declare. Icon availability depends on someone else's infrastructure, and the documentation states plainly that the servers are free to use but not free to run, and asks for financial support. There is no service level agreement there and no paid plan with a guarantee. On top of that comes the flicker on first render, partly softened by the ssr and fallback props in the component for React.

The third road is running your own API. The server code is open, the @iconify/api package sits at version 3.2.0 from 28 November 2025 under MIT, and the documentation describes deployment from the repository, from npm and from a container image. You point the component at your own address through addAPIProvider.

Code
TypeScript
import { addAPIProvider, Icon } from '@iconify/react'

addAPIProvider('local', {
  resources: ['https://icons.example.com']
})

// from now on a name with the provider prefix hits your own server
export const Save = () => <Icon icon="@local:mdi:content-save" />

Icon set licensing, the place where audits fall over

Iconify's code is uniformly open. The license.txt file in the iconify/iconify repository holds the MIT text with a copyright notice for Vjacheslav Trushkin covering 2021 to the present, the npm license field for @iconify/react, @iconify/vue, @iconify/svelte, @iconify/utils and @iconify/tools reads MIT, and the published @iconify/react 6.0.2 tarball contains a license.txt file with the full MIT text. Three sources agree, no surprises.

The data is an entirely different matter, and this is a real legal problem rather than a formality.

The @iconify/json 2.2.519 package declares MIT in npm. Its contents, however, hold no licence file in the root directory. The only file with licence text sits at lib/license.txt and covers the helper Finder class for PHP, with a copyright notice for 2017 and 2018. The iconify/icon-sets repository likewise has no licence file in its root: the paths license.txt, LICENSE and LICENSE.md all return 404. The MIT field therefore describes helper code, not the icons.

The icons carry their own licences, recorded in the info.license field of each json/<prefix>.json file. The field has three subfields: title with a human-readable name, spdx with an identifier and url with an address for the licence text. The same data is collected in collections.json and in the readable collections.md in the repository. The licence distribution in release 2.2.519 looks like this.

Licence groupNumber of setsWhat it means in practice
MIT106no restrictions, copyright notice required
CC BY 4.0 and CC BY 3.053attribution required in the product
Apache 2.031permissive, with a patent clause
Open Font License13selling the icons alone as a file is barred
CC BY-SA 4.0 and 3.09derivative works under the same licence
CC0 and Unlicense11public domain
GPL 2.0 and GPL 3.06reciprocal licence, one for your lawyer
ISC, MPL 2.0, BSD 3-Clause5permissive
CC BY-NC 4.0 and CC BY-NC-SA 4.02commercial use forbidden

The total is 236, which matches the number of sets in collections.json.

A concrete example of a set that demands attribution: fa6-brands, that is Font Awesome 6 Brands, 495 icons, licensed CC BY 4.0, author Dave Gandy. Likewise solar with 7,608 icons and selfhst with 7,107 icons. If you use a single brand icon from fa6-brands in your interface, the licence requires you to name the author and identify the licence.

Two sets are off limits in a commercial product: cbi, named Custom Brand Icons, 1,718 icons under CC BY-NC-SA 4.0, and ps, named PrestaShop Icons, 479 icons under CC BY-NC 4.0. Six sets sit under GPL-family licences, among them dashicons with 342 icons and wordpress with 341 icons under GPL-2.0-or-later, plus icomoon-free with 491 icons under GPL-3.0-or-later. Icons are not executable code, so how far the reciprocity reaches is arguable, but that is exactly the kind of argument you do not want to be having in a commercial project.

Checking the licence of a given set before use takes a few lines.

Code
TypeScript
import { lookupCollections } from '@iconify/json'

const collections = await lookupCollections()
const risky = Object.entries(collections)
  .filter(([, info]) => /NC|GPL|SA/.test(info.license.spdx ?? ''))
  .map(([prefix, info]) => `${prefix}: ${info.license.spdx}`)

console.log(risky.join('\n'))

The same reading without installing anything comes from a GET https://api.iconify.design/collections request, which returns an object with the same license.title, license.spdx and license.url fields.

Single-set packages behave differently from the bundled @iconify/json, and in your favour. @iconify-json/mdi 1.2.3, dated 20 January 2025, declares Apache-2.0 in npm, that is the licence of the set itself rather than MIT. That is good news for automated dependency auditing. The bad news is that the tarball contains no licence file whatsoever. Its nine files are index.js, index.mjs, index.d.ts, icons.json, info.json, metadata.json, chars.json, package.json and README.md. You have to attach the Apache 2.0 text to your product yourself, fetching it from the address in the info.license.url field.

One last detail that can mislead you while transcribing a licence list: the footer of the iconify.design documentation site mentions release under the Apache 2.0 licence with copyright held by Iconify OÜ, while the licence file in the code repository is MIT. The footer covers the documentation content; the library code is under MIT.

The compiled route: unplugin-icons and the Tailwind plugins

A third approach skips the runtime component entirely. unplugin-icons turns an import of a virtual path into a finished component at build time, so the browser receives plain SVG code with no library hunting for it.

TSvite.config.ts
TypeScript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import Icons from 'unplugin-icons/vite'
import { FileSystemIconLoader } from 'unplugin-icons/loaders'

export default defineConfig({
  plugins: [
    react(),
    Icons({
      compiler: 'jsx',
      jsx: 'react',
      scale: 1.2,
      defaultClass: 'icon',
      autoInstall: false,
      customCollections: {
        brand: FileSystemIconLoader('./src/assets/icons')
      }
    })
  ]
})

In application code you import an icon through the path ~icons/mdi/content-save and the plugin resolves it to a component. The virtual:icons prefix still works in Vite, but the documentation recommends ~icons for consistency across build tools. The autoInstall option adds missing set packages to the project and is flagged as experimental in the documentation, so on a build server it is better left switched off.

The plugin is ESM only, which its documentation states explicitly. The @svgr/core, @svgx/core, @vue/compiler-sfc and svelte peer dependencies are marked optional in the peerDependenciesMeta field, so your package manager will not demand packages for frameworks you do not use. Examples in the repository cover Vite with React and with Vue 3, Next.js, Nuxt 4, SvelteKit and Astro.

The Tailwind plugins stand apart; they generate CSS classes that render an icon as a background or a mask image, with no element in the document tree at all.

There is one number here that the sources report differently. The npm registry reports an unpackedSize of 461,996,959 bytes for @iconify/json 2.2.519, and unpacking the archive yields 442 MiB, of which 441 MiB is the json directory alone. The unplugin-icons documentation, at the same moment, speaks of roughly 120 MB. The gap is more than twofold, so when planning build server cache space, budget by the registry figure. Only the icons you use reach the application bundle, but downloading and unpacking covers the whole thing.

Iconify against Lucide and single sets

Lucide, covered separately, is a different kind of tool, and comparing the two head on leads you astray.

CriterionIconifyLucideSingle set package
Icon count334,616 across 236 sets1,776 in one setdepends on the set
Visual consistencynone across setsone style, one gridwithin the set
Data licenceseparate for each setISC with a separate MIT note for icons from Featherone, declared in npm
Default data sourcepublic API over the networknpm package, localnpm package, local
Install footprint442 MiB with @iconify/jsonone package per frameworka few to a few dozen MB
When to reach for iticon choice is not settled, logos neededa product with one interface stylea single known target set

A sensible choice looks like this. If you are building a product with its own visual language and you know one consistent set will do, take that set directly. Lucide is present in Iconify as lucide with 1,776 icons under ISC, so you lose nothing by going through Iconify, but you gain nothing either beyond an extra layer. The same goes for technology logos, which you will find tidied up and licence-documented in svgl.

Iconify wins in three situations. The first is an editor or configurator where the user picks the icon, because then the API search engine and access to every set are the point of the feature. The second is an admin panel glued together from many sources, where you need icons from several families at once anyway, brand logos next to interface icons for instance. The third is a prototype where you do not want to commit to a set yet.

In projects built on Nuxt there is also the nuxt-icon module, and in component libraries of the shadcn/ui kind the icons arrive already chosen alongside the components, so adding Iconify next to them creates two parallel icon systems in one application.

Common mistakes

Installing @iconify/iconify on the strength of an older tutorial. The package has been frozen since June 2023 and carries an npm annotation about its replacement by iconify-icon. Version 3.1.1 will still install and still work, but it will receive no more fixes.

Leaving API mode on in production without a deliberate decision. Importing from @iconify/react without the /offline suffix means every user queries a server you do not control. That is sometimes acceptable, but it has to be a choice rather than a side effect of a copied example.

Assuming that because @iconify/json is marked MIT in npm, all the icons are MIT. The field describes helper code. The icons carry 19 different licences, two of which forbid commercial use.

Pulling a whole set into the application bundle through import icons from '@iconify-json/mdi/icons.json'. That file is over three megabytes and holds 7,447 icons. For offline mode, prepare a subset with getIcons.

Confusing @iconify/tailwind with @iconify/tailwind4. These are not versions of one package but two packages targeting the third and fourth versions of Tailwind, released independently.

Relying on the icon search engine without checking the info.license field of the chosen set. The catalogue shows a licence next to every set, but nothing enforces reading it, and copying an icon name out of the search results is quicker than reading metadata.

FAQ

Which Iconify package should I pick for a new React project?

@iconify/react at version 6.0.2, or the iconify-icon 3.0.2 web component with the @iconify-icon/react 3.0.3 wrapper. Choosing between them comes down to whether you want a custom element in the document tree. Do not install @iconify/iconify, which has carried an npm replacement annotation since June 2023.

Is icon data loaded over the network by default?

Yes. Importing from @iconify/react without a suffix uses the public API at https://api.iconify.design. To switch that off, import from @iconify/react/offline and register data with addCollection or addIcon. The offline entry point contains no network code.

How do I check the licence of a specific set?

Read the info.license field from the json/<prefix>.json file in the iconify/icon-sets repository, use the lookupCollections() function from the @iconify/json package, or call GET https://api.iconify.design/collections. All three routes return title, spdx and url.

Can I use any Iconify icon in a commercial product?

No. Two sets out of 236 carry licences forbidding commercial use: cbi under CC BY-NC-SA 4.0 and ps under CC BY-NC 4.0. Another 53 require attribution, and six sit under GPL-family licences.

How much space does the full data set take?

The npm registry reports an unpacked size of 461,996,959 bytes for @iconify/json 2.2.519, and measuring the unpacked directory gives 442 MiB. The unplugin-icons documentation says roughly 120 MB, which does not match. Only the icons you use reach the application bundle.

Does the public API have rate limits or a paid plan?

The documentation states no limit and no pricing. It describes the servers as free to use, asks for financial support of the project and lists three addresses for redundancy. The absence of a service level agreement matters here: if icon availability is critical, run your own API on the @iconify/api 3.2.0 package.

Read next

We use cookies to enhance your experience on the site