CodeWorlds
Back to collections
Guide18 min readCodeWorlds Team

Lucide, an SVG icon set for many frameworks

Lucide 1.33.0 ships 1776 SVG icons under ISC with a separate MIT note from Feather. Package size, name aliases, version 1.0 and a comparison with Iconify.

Lucide, an SVG icon set for many frameworks

Lucide is an open icon set drawn with a single stroke, released as separate packages for React, Vue, Svelte, Solid, Angular and plain JavaScript. Version 1.33.0 of lucide-react landed in the npm registry on 19 August 2026 under the ISC licence and contains 1776 icons. Below I describe what follows from that in practice, including the parts the documentation leaves out.

What the Lucide packages give you

Every icon is a component that renders an svg element straight into the document tree. There is no sprite, no icon font and no network request for data. The default attributes live in one module and read as follows: xmlns set to the SVG namespace, width and height equal to 24, viewBox equal to 0 0 24 24, fill set to none, stroke set to currentColor, strokeWidth equal to 2, and strokeLinecap and strokeLinejoin set to round.

The currentColor value on the stroke attribute is the most important design decision here. The icon inherits the parent's text colour, so a text-red-500 class from Tailwind CSS paints the icon with no extra code, and dark mode works by itself as long as the text colour is set at the container level.

The package family at version 1.33.0, published on 19 August 2026, covers lucide for plain JavaScript, lucide-react for React, @lucide/vue, @lucide/svelte, lucide-solid, @lucide/angular and lucide-static with SVG files and a font. All of them declare the ISC licence. The peer dependency ranges are disjoint and do not collide, because each package names only its own framework: lucide-react accepts React in the range ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0, @lucide/angular requires @angular/core and @angular/common at version 17 or later, lucide-solid requires solid-js in the range ^1.4.7, and @lucide/svelte accepts Svelte 5 only. Lucide packages do not depend on one another, so installing two at once does not end in a version conflict.

@lucide/lab is developed separately and sits at version 0.2.0 from 31 July 2026. It is a waiting room for icons that did not make it into the main set, and the sub-one numbering is an honest signal: those icons may disappear or change shape.

What Lucide does not have. There are no brand icons or logos, because they were removed in version 1. There is no filled variant, which the documentation confirms: the page about fills opens with the sentence that fills are officially not supported, and shows a workaround that passes fill together with strokeWidth={0} on the Star and StarHalf icons. It works on some icons and fails on the rest, because the shapes were not drawn with closed outlines in mind.

Code
Bash
# installation for React, pinning the exact version
npm install lucide-react@1.33.0

# the plain JavaScript variant
npm install lucide@1.33.0

# SVG files and the font only, without the component layer
npm install lucide-static@1.33.0

The ISC licence and the trace of Feather

Lucide started as a fork of the Feather project, which was under the MIT licence. The declaration in the npm registry says a curt ISC and that is all most dependency scanners see. The licence file in the repository says more.

The root of lucide-icons/lucide holds a LICENSE file, with no LICENSE.md variant, running to 43 lines and 3208 bytes. It opens with the full ISC text and a copyright notice reading "Copyright (c) 2026 Lucide Icons and Contributors". A separator follows, then a sentence stating that the icons listed below are derived from the Feather project. The list holds exactly 115 names, from airplay to zoom-out, and beneath it stands the full MIT text with the notice "Copyright (c) 2013-present Cole Bemis", the author of Feather.

The third source, the contents of the published package, matches the repository byte for byte. The unpacked lucide-react 1.33.0 tarball contains a LICENSE file identical to the one on the main branch, and the build script in package.json does exactly cp ../../LICENSE ./LICENSE, so there is no room for a mismatch. Every file in the dist directory additionally opens with a @license lucide-react v1.33.0 - ISC header.

What this means for someone filling in a licence inventory in a commercial project. ISC and MIT are functionally equivalent and both require the copyright notice to be preserved in distribution. The difference is that the inventory needs two notices rather than one: Lucide Icons and Contributors for the whole set, and Cole Bemis for those 115 icons. An automated scanner reading the license field from the npm registry sees only ISC and simply loses the Feather notice. If your company requires full licence texts to be shipped with a release, including the LICENSE file in its entirety is enough, because both notices sit inside it together.

There is one more small trap in the list itself. The Feather names are recorded in their historical form, so you will find alert-circle, alert-triangle and more-horizontal there, while in current Lucide those icons carry different primary names and survive only as aliases. The list concerns the origin of the shape, not the current import name, and trying to match it automatically against the component names used in your code produces false results in both directions.

Version 1.0 and what fell out of it

Until March 2026 the numbering ran as 0.x, where each new number after the dot meant new icons, and the last release in that series was 0.544.0 from 11 September 2025. Version 1.0.0 reached npm on 23 March 2026, but the release note on GitHub opens with a warning that it was published unintentionally and that 1.0.1 should be used instead. The correction shipped the same day, roughly an hour later, and it carries the real release note.

The breaking changes in version 1 are specific. Brand icons were removed, and the migration note lists fourteen names: Chromium, Codepen, Codesandbox, Dribbble, Facebook, Figma, Framer, Github, Gitlab, Instagram, LinkedIn, Pocket, RailSymbol and Slack. The documentation points to the brands' own official assets or to the Simple Icons collection for replacements. The UMD build was also removed for every package except lucide, leaving ESM and CJS. The aria-hidden attribute is now set by default. The lucide-vue-next package was renamed to @lucide/vue, and Angular received a new, standalone implementation in @lucide/angular. Context providers were added for React, Vue, Svelte and Solid, the font code points were stabilised, and shadow DOM support landed in the lucide package.

The side effect is visible in the registry. The old packages froze on the doorstep of version one: lucide-vue-next sits at 1.0.0 and lucide-svelte at 1.0.1, both from 23 March 2026. This matters for projects on Svelte 3 or 4, because @lucide/svelte accepts Svelte 5 only, and the frozen lucide-svelte will get no new icons.

The release pace is otherwise high. Counting by npm publication dates, twelve minor releases shipped between 28 June and 19 August 2026, from 1.22.0 to 1.33.0, and the total number of lucide-react versions in the registry has passed 685. The repository has roughly 24.1 thousand stars and 1.5 thousand forks, with about 290 open issues and 271 open pull requests. That last number is mostly proposals for new icons awaiting design review, which at this set size is a bottleneck by its very nature.

Package size, the most common surprise

This is where the real problem sits, the one most texts about Lucide stay quiet about. The lucide-react package at version 1.33.0 unpacks to 31.4 megabytes across 4108 files. The plain JavaScript version takes 21.1 megabytes, and lucide-static a full 48.3 megabytes. This is not a size that reaches the browser, but it does reach the disk, the package manager cache and the container image with the build stage.

The structure looks like this. The dist/esm/icons directory holds 2034 modules. Of those, 1776 are the icons proper, 257 are modules kept under old names for backwards compatibility, and one, index.mjs, is a barrel file holding no icon at all. A single module is small: camera.mjs weighs 692 bytes and exports an __iconNode array describing the shapes plus a ready component. The main entry file dist/esm/lucide-react.mjs is 1791 lines of pure re-exports, and the sideEffects field in package.json is set to false. When building ESM with working dead code elimination, importing three icons ends with three modules in the output, and this is the path Lucide recommends.

The trouble starts off that path. The CJS variant is a single dist/cjs/lucide-react.js file of 978,743 bytes with all 1776 icons written into it. There are no per-icon CJS modules. Any environment that reaches for the main field, meaning an older Jest without ESM transformation, some server-side rendering configurations and dependency analysis tooling, will load the full 956 kilobytes to obtain a single arrow. On top of that, package.json has no exports field at all, so deep imports such as lucide-react/dist/esm/icons/camera.mjs technically work but rest on a directory layout nobody promised to keep between releases.

The second surprise concerns types. The dist/lucide-react.d.ts file weighs 2,293,869 bytes, and its two variants sit next to it, lucide-react.prefixed.d.ts and lucide-react.suffixed.d.ts, each around 2.2 megabytes. The bulk comes from a JSDoc comment attached to every icon carrying a preview as an SVG image encoded in base64. The convenience in the editor is real, and so is the cost: the TypeScript language server has to chew through this on every project open.

The browser build from a CDN has no dead code elimination whatsoever. The dist/umd/lucide.min.js file from the lucide package weighs 419,264 bytes, roughly 97 kilobytes after gzip compression, and contains the complete set. The documentation for the plain JavaScript package itself attaches a warning comment to the createIcons({ icons }) call stating that this will import and bundle all the icons.

Code
JavaScript
// the recommended static import, one icon means one module in the build output
import { Camera, ChevronRight } from 'lucide-react'

export function Toolbar() {
  return (
    <div className="text-slate-600">
      <Camera size={20} strokeWidth={1.5} />
      <ChevronRight size={20} absoluteStrokeWidth />
    </div>
  )
}

If the icon name comes from a database or a content management system, DynamicIcon exists for that, but the documentation openly advises against it for static cases and lists four costs: all icons are imported at build time, which lengthens the build; the bundler creates a separate module per icon, which increases the number of network requests; a flash is visible while loading; and with server-side rendering you have to make sure the icon is available during the initial render.

Code
JavaScript
// dynamic icon selection by name, together with a fallback component
import { DynamicIcon, iconNames } from 'lucide-react/dynamic'
import { LucideProvider } from 'lucide-react'

const Placeholder = () => <span className="block h-6 w-6 rounded bg-slate-200" />

export function CmsIcon({ name }) {
  const safeName = iconNames.includes(name) ? name : 'circle-help'
  return (
    <LucideProvider size={24} strokeWidth={2} color="currentColor">
      <DynamicIcon name={safeName} fallback={Placeholder} />
    </LucideProvider>
  )
}

Icon attributes and context

The component accepts color, size, strokeWidth, absoluteStrokeWidth and className, and otherwise passes any SVG attribute through to the element. The LucideProps type extends the SVG attributes with two fields of its own: size of type string | number and absoluteStrokeWidth of type boolean.

absoluteStrokeWidth solves a specific problem. By default the stroke width is expressed in the icon's coordinate system units, so scaling the icon to 48 pixels also doubles the stroke width. With the flag set, the component computes the width using the formula strokeWidth * 24 / size, which keeps the stroke at a constant weight regardless of size. For icons placed next to text at different type sizes this is a difference you see with the naked eye.

The context introduced in version 1 lets you set those values once for a subtree. LucideProvider accepts size, color, strokeWidth, absoluteStrokeWidth and className, and each of them can be overridden on an individual icon, because the prop value takes precedence over the context value. The useLucideContext hook is exported in case you need to read those settings inside your own component.

Accessibility is handled sensibly. The component checks whether any of the passed props start with aria-, or equal role, or equal title. If none of those are present and the icon has no children, it adds aria-hidden="true". A decorative icon therefore drops out of the accessibility tree with no work on your part, while an icon carrying meaning requires an explicit aria-label, which is the right order of incentives.

Finally, the CSS classes. The element always receives a lucide class, the class from context, the class passed in className and a class with the icon name in the form lucide-camera. This is handy for global styling, but with Tailwind CSS you have to remember that the library's own classes are prepended before your own and that order in the attribute does not decide specificity.

Names, aliases and autocomplete noise

The main entry file exports 6092 distinct names for 1776 icons, roughly three and a half names per icon. This comes from three naming styles plus historical aliases. The house icon is available as House, HouseIcon and LucideHouse, and on top of that the old name Home leads to the same module. Similarly edit-2 was renamed to pen, while sort-desc lives on as an alias of arrow-down-wide-narrow.

The side effect shows up on the first keystroke in the editor: autocompletion buries the list under variants of the same icon. The documentation proposes two steps. The first is switching off automatic imports from this package by setting js/ts.preferences.autoImportFileExcludePatterns in .vscode/settings.json. The second is a custom declaration file that redirects the module to a variant with a single naming style.

TSlucide-react.d.ts
TypeScript
// lucide-react.d.ts, usually inside the project's @types directory
declare module 'lucide-react' {
  // suffixed variant: HouseIcon, CameraIcon, ChevronRightIcon
  export * from 'lucide-react/dist/lucide-react.suffixed'
  // alternatively the prefixed variant:
  // export * from 'lucide-react/dist/lucide-react.prefixed'
}

A separate matter is the ambiguity of the names themselves at this set size. With 1776 icons it is hard to guess whether the shape you want is called file-text, notebook-text or scroll-text, and each of those exists and looks different. The project site's public API at lucide.dev/api/tags returns a map from icon name to a list of keywords and holds exactly 1776 entries, which matches the package contents. That is the fastest way to build your own search or to check in a script whether a name used in editorial content actually exists.

Code
JavaScript
// plain JavaScript: import only the icons you need, not the whole icons object
import { createIcons, Menu, ArrowRight, Globe } from 'lucide'

createIcons({
  icons: {
    Menu,
    ArrowRight,
    Globe
  }
})

Lucide next to Iconify, Heroicons and Radix Icons

The difference lies primarily in the distribution model, not the icon count. Lucide gives you a package per framework and a component per icon, which makes the package enormous and the build output small. Iconify inverts that arrangement: one small universal component pulls icon data from an API or from separate set packages, which gives you access to many sets through a single interface but introduces either a runtime dependency on a data source or an extra configuration step for offline mode.

SetDistribution modelLicenceVersion and npm publication dateUnpacked package
Lucide, lucide-reactpackage per framework, component per icon, 1776 iconsISC, plus an MIT notice for 115 icons from Feather1.33.0, 19 August 202631.4 MB, 4108 files
Iconify, @iconify/reactone component, icon data from an API or from set packagesMIT6.0.2, 15 September 20250.21 MB, 16 files
Heroicons, @heroicons/reactone package, four variants: 24 outline, 24 solid, 20 solid, 16 solidMIT2.2.0, 18 November 20243.7 MB, 5183 files
Radix Icons, @radix-ui/react-iconsone package, 318 icons on a 15 by 15 gridMIT1.3.2, 14 November 20243.4 MB, 332 files

Heroicons carries about 325 icons in the 24 outline variant and the same number in 24 solid, so it provides what Lucide lacks: a ready filled variant. The price is a far smaller set. Radix Icons aims at dense interface layouts, draws on a 15 by 15 grid and holds 318 icons, but the last release dates from November 2024, so when choosing it is fair to treat it as a closed set rather than an actively growing one. If you use the rest of the Radix UI family, visual consistency still argues in its favour.

Lucide's popularity stems largely from a single decision made by another project. Components generated by shadcn/ui import icons directly from lucide-react: accordion reaches for ChevronDown, dialog for X, and select for Check, ChevronDown and ChevronUp. Note here that the dependencies field in those items' registry descriptions lists only the matching Radix package, so lucide-react has to be present in the project already. The same dependency propagates through libraries built on that foundation, including Magic UI, while HeroUI goes its own way and does not impose this set.

A quite different approach comes from Iconify: instead of one set it gives one interface to 236 sets and over three hundred thousand icons, so you will find a filled variant and brand icons there, neither of which Lucide has. Two things need weighing though. The component’s default mode fetches icon data from the vendor’s public server, which means a request to someone else’s infrastructure on every render and no availability agreement of any kind. The second is licensing: the sets carry their own terms, two of them forbid commercial use and six sit on GPL family licences, so the audit has to run per set rather than once for the whole thing.

Common mistakes

Importing the whole icons object in the plain JavaScript package and calling createIcons({ icons }). This pulls the complete set into the build output and is the one place where the documentation itself puts a warning inside the example code.

Assuming any bundler will eliminate the dead code. Elimination works for ESM, where each icon is a separate module, and not for the CJS path, where everything sits in one 956 kilobyte file. If something in your test configuration or server-side rendering step reaches for the main field, check what actually got loaded.

Deep imports such as lucide-react/dist/esm/icons/camera.mjs in the hope of a smaller output. The package has no exports field, so such a path works by accident rather than by contract, and it may vanish when Lucide changes its build configuration.

Setting the icon size through width and height in CSS while leaving strokeWidth untouched. The stroke scales along with the icon, which makes a 48 pixel icon look heavier than the text around it. The proper answer is absoluteStrokeWidth.

Assuming brand icons are still in the set. In code predating version 1, imports such as Github or Figma worked; after upgrading to version one they stop compiling and have to be replaced with a custom SVG or another set.

Writing plain ISC into the licence inventory based on the field from the npm registry. The MIT notice for the 115 icons inherited from Feather sits in the same licence file, and a scanner reading package metadata alone will not see it.

FAQ

How many icons does Lucide 1.33.0 have

Exactly 1776. That is how many icon modules are re-exported from the main entry file of lucide-react, and that is how many entries the project site's public API at lucide.dev/api/tags returns. The dist/esm/icons directory, however, holds 2034 modules, because 257 of them are old names kept for backwards compatibility and one is the index.mjs barrel file.

Does Lucide have a filled variant

No. The documentation states plainly that fills are officially not supported, and shows a workaround with the fill attribute plus strokeWidth={0} using the Star and StarHalf icons as an example. It works on icons whose outlines are closed and fails on the rest. If a filled variant is a project requirement, Heroicons provides one across the board.

Is the ISC licence enough for a commercial project

Yes, ISC is a permissive licence, allows commercial use and requires only that the copyright notice be preserved. You do, however, have to carry both notices from the LICENSE file into your inventory, meaning ISC for the whole set and MIT with Cole Bemis's copyright for the 115 icons originating from Feather. The simplest route is to ship that file whole.

Why does the package weigh 31 megabytes if Lucide advertises dead code elimination

Because those are two different things. What sits on disk is the complete set of 1776 icons in three forms, ESM, CJS and type declarations, with the .d.ts files alone taking over 6.6 megabytes because of the SVG previews inside comments. Only the icons you actually import reach the browser, provided you build through ESM.

Which package should I pick for Svelte

For Svelte 5 there is @lucide/svelte at version 1.33.0. For Svelte 3 and 4 lucide-svelte remains, but that package stands at 1.0.1 from 23 March 2026 and will receive no new icons. By analogy, in Vue the current name is @lucide/vue, while lucide-vue-next froze at 1.0.0.

Can Lucide be used without a package manager

It can, through the UMD file from a CDN, but without dead code elimination. The dist/umd/lucide.min.js file weighs 419,264 bytes, roughly 97 kilobytes after compression, and contains every icon. The documentation advises against pointing at @latest and recommends pinning an exact version in the URL.

Read next

We use cookies to enhance your experience on the site