React & Modern UI Frameworks

How to Use Lucide Icons in React, Next.js & Web Apps: The Complete 2026 Guide

Developer workstation with 3D isometric glass grid displaying glowing Lucide vector icons and Next.js React code
Figure 1: Lucide Icons on an isometric 24px coordinate grid — crisp 2px line geometry optimized for modern UI design systems.

1. What is Lucide Icons? Architecture & History

In 2020, Cole Bemis' iconic Feather Icons project ceased active maintenance at 287 icons. Developers loved Feather's clean 24x24 grid, rounded corners, and consistent 2px stroke, but modern web applications quickly ran out of essential glyphs for notifications, e-commerce, cloud infrastructure, and data visualization.

Lucide was born as an open-source, community-driven fork designed to preserve Feather's design DNA while dramatically scaling the library. Today, Lucide provides:

  • 1,490+ Vector Glyphs: Covering navigation, multimedia, code editors, security, communications, and database symbols.
  • Standardized 24x24 Pixel Grid: Every single icon is drafted on a 24x24 coordinate space with a 2px stroke and rounded caps (stroke-linecap="round" stroke-linejoin="round").
  • Native Multi-Framework Bindings: Dedicated packages for React, Vue, Svelte, Angular, Preact, and vanilla DOM scripting.
  • Strict Tree-Shakability: Designed from the ground up as individual ES module exports to prevent bundle bloat.
  • The Foundation of shadcn/ui: Lucide is the default icon library bundled with Radix UI and shadcn/ui components.

For a direct head-to-head comparison of glyph differences and metrics between the original set and its modern successor, see our in-depth Lucide vs Feather Icons guide.

2. Installation & Framework Packages

Lucide maintains first-class packages for all major JavaScript frameworks. Install the appropriate package for your tech stack using your package manager of choice:

# React / Next.js / Remix
npm install lucide-react

# Vue 3 / Nuxt
npm install lucide-vue-next

# Svelte 5 / SvelteKit
npm install lucide-svelte

# Angular 19+
npm install lucide-angular

# Vanilla JavaScript / Node
npm install lucide

Quick Start in React 19 / Vite

In a modern React application, simply import named icon components from lucide-react. Each component returns an accessible, inline SVG element:

import React from 'react';
import { ShieldCheck, ArrowRight, Bell } from 'lucide-react';

export function SecurityCard() {
  return (
    <div className="p-6 bg-neutral-900 border border-neutral-800 rounded-xl flex items-center justify-between">
      <div className="flex items-center gap-4">
        <div className="p-3 bg-emerald-950/50 border border-emerald-800/40 rounded-lg text-emerald-400">
          <ShieldCheck size={28} strokeWidth={1.75} />
        </div>
        <div>
          <h3 className="text-white font-semibold text-base">Two-Factor Authentication</h3>
          <p className="text-neutral-400 text-sm">Hardware security token verified.</p>
        </div>
      </div>
      <button className="flex items-center gap-2 px-4 py-2 bg-neutral-800 hover:bg-neutral-700 text-white rounded-lg text-sm font-medium transition-colors">
        <span>Manage</span>
        <ArrowRight size={16} />
      </button>
    </div>
  );
}

3. Tree-Shaking Benchmarks: Avoiding the 1.2 MB Bundle Trap

Because lucide-react contains over 1,490 icon components, improper import patterns can inadvertently force your bundler (Vite, Webpack, Turbopack, or Rollup) to include the entire icon collection in your client payload.

The Deadly Import Mistake

Never write: import * as Lucide from 'lucide-react'; or import * as icons from 'lucide-react';. Even in modern bundlers, referencing the full namespace defeats static dead-code elimination, inflating your bundle by up to 1,240 KB uncompressed!

Import Pattern Icons Used Raw Bundle Size Gzip Size Tree-Shaking Status
import * as icons from 'lucide-react' 5 icons 1,248.6 KB 342.1 KB Fails (Whole library included)
import { Home, Bell, ... } from 'lucide-react' 5 icons 6.8 KB 2.1 KB Optimal (1.36 KB / icon)
import Home from 'lucide-react/dist/esm/icons/home' 5 icons 6.5 KB 2.0 KB Manual Subpath (Guaranteed)
IconStash SVG Component Copy 5 icons 3.2 KB 1.1 KB Zero Package Dependency

For applications using Next.js 15 or Vite 6, named imports like import { Search } from 'lucide-react' tree-shake cleanly out of the box. If you are stuck on an older Webpack 4 or Create React App setup with faulty ESM side-effects resolution, use the direct subpath import pattern to guarantee zero leakage.

4. Next.js 15 App Router & React Server Components

A common misconception among developers transitioning to the Next.js App Router is that icon libraries require the 'use client' directive. This is completely false for Lucide Icons.

Lucide React components are pure, stateless functions. They generate plain vector markup using SVG primitive elements (<svg>, <path>, <circle>, <rect>). Because they use no hooks (useState, useEffect) and attach no event listeners, they execute flawlessly inside React Server Components (RSC):

// app/dashboard/page.tsx (Server Component - NO 'use client' needed!)
import { CheckCircle2, AlertTriangle, Clock } from 'lucide-react';
import { getSystemMetrics } from '@/lib/metrics';

export default async function DashboardPage() {
  const metrics = await getSystemMetrics();

  return (
    <main className="max-w-5xl mx-auto p-8">
      <h1 className="text-2xl font-bold text-white mb-6">Operational Status</h1>
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
        <div className="p-5 bg-neutral-900 border border-neutral-800 rounded-xl">
          <div className="flex items-center gap-3 text-emerald-400 mb-2">
            <CheckCircle2 size={20} />
            <span className="font-semibold text-sm">Database Healthy</span>
          </div>
          <p className="text-2xl font-bold text-white">{metrics.dbLatency} ms</p>
        </div>

        <div className="p-5 bg-neutral-900 border border-neutral-800 rounded-xl">
          <div className="flex items-center gap-3 text-amber-400 mb-2">
            <AlertTriangle size={20} />
            <span className="font-semibold text-sm">Cache Eviction Rate</span>
          </div>
          <p className="text-2xl font-bold text-white">{metrics.cacheEvictions}</p>
        </div>

        <div className="p-5 bg-neutral-900 border border-neutral-800 rounded-xl">
          <div className="flex items-center gap-3 text-blue-400 mb-2">
            <Clock size={20} />
            <span className="font-semibold text-sm">Average Response</span>
          </div>
          <p className="text-2xl font-bold text-white">{metrics.responseTime} ms</p>
        </div>
      </div>
    </main>
  );
}

When rendered on the server, the icons stream down as static HTML string tokens. Zero bytes of Lucide JavaScript are shipped to the client browser, resulting in pristine Core Web Vitals scores and near-instant First Contentful Paint (FCP).

5. Dynamic Icon Imports: Safe CMS & Database Rendering

Enterprise SaaS dashboards, content management systems, and navigation menus frequently store icon identifiers as database strings (e.g., "settings", "users", "credit-card"). Beginners often attempt to resolve these dynamically via:

// ANTI-PATTERN: Breaks tree-shaking and bundles the entire 1.2 MB library!
import * as LucideIcons from 'lucide-react';

export function BadDynamicIcon({ name }: { name: string }) {
  const IconComponent = (LucideIcons as any)[name];
  return IconComponent ? <IconComponent /> : null;
}

The Solution: dynamicIconImports with Next.js

Lucide officially provides a dedicated entrypoint: lucide-react/dynamicIconImports. This module exposes a typed map of asynchronous icon loaders. Coupled with next/dynamic or React.lazy, it downloads only the specific icon chunk required at runtime:

// components/DynamicIcon.tsx
'use client';

import dynamic from 'next/dynamic';
import dynamicIconImports from 'lucide-react/dynamicIconImports';
import { LucideProps } from 'lucide-react';

export type IconName = keyof typeof dynamicIconImports;

interface DynamicIconProps extends LucideProps {
  name: IconName;
  fallback?: React.ReactNode;
}

export function DynamicIcon({ name, fallback, ...props }: DynamicIconProps) {
  const IconLoader = dynamicIconImports[name];

  if (!IconLoader) {
    return fallback ? <>{fallback}</> : <div className="w-6 h-6 bg-neutral-800 rounded animate-pulse" />;
  }

  const Component = dynamic(IconLoader, {
    loading: () => fallback ? <>{fallback}</> : <div className="w-6 h-6 bg-neutral-800 rounded animate-pulse" />,
    ssr: true,
  });

  return <Component {...props} />;
}

This pattern ensures that an admin menu displaying 12 dynamic icons downloads exactly 12 micro-chunks (~1.2 KB each) instead of the entire 1,490-icon library.

6. Customizing Props, Colors & Tailwind CSS

Every Lucide icon component accepts a comprehensive set of props extending standard SVG attributes:

Prop Name Type Default Description
size number | string 24 Sets width and height in pixels or CSS units (e.g., "1.5rem").
color string "currentColor" Overrides the stroke color. Defaults to cascading from parent CSS color.
strokeWidth number | string 2 Line stroke thickness. Accepts values from 0.5 to 3.5.
absoluteStrokeWidth boolean false Maintains visual stroke thickness regardless of icon scaling factors.
className string "" Accepts custom CSS classes or Tailwind utility classes.

Tailwind CSS v4 Integration

In modern Tailwind CSS development, using utility classes on the className prop is the cleanest pattern. You can control sizing with size-*, color with text utilities, and stroke width with arbitrary values:

import { Sparkles } from 'lucide-react';

export function PremiumBadge() {
  return (
    <div className="inline-flex items-center gap-2 px-3 py-1.5 bg-neutral-900 border border-neutral-800 rounded-full group cursor-pointer hover:border-lime-500/40 transition-colors">
      <Sparkles className="size-4 text-neutral-400 group-hover:text-lime-400 stroke-[1.75] transition-colors group-hover:rotate-12 duration-300" />
      <span className="text-xs font-semibold text-neutral-300 group-hover:text-white">Pro AI Engine</span>
    </div>
  );
}

7. Building Custom Icons with createLucideIcon()

Design teams often need proprietary company logos or domain-specific icons that blend seamlessly with Lucide's 24x24 coordinate style. Lucide exports an internal helper function called createLucideIcon() specifically for this purpose:

// components/icons/CustomDatabaseIcon.tsx
import { createLucideIcon } from 'lucide-react';

/**
 * CustomDatabaseIcon crafted on Lucide's 24x24 grid with 2px stroke.
 * Accepts all standard LucideProps (size, strokeWidth, color, className).
 */
export const CustomDatabaseIcon = createLucideIcon('CustomDatabase', [
  ['ellipse', { cx: '12', cy: '5', rx: '9', ry: '3', key: 'e1' }],
  ['path', { d: 'M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5', key: 'p1' }],
  ['path', { d: 'M3 12c0 1.66 4 3 9 3s9-1.34 9-3', key: 'p2' }],
  ['line', { x1: '12', y1: '5', x2: '12', y2: '22', key: 'l1' }]
]);

By declaring icon geometry as lightweight JSON tuples rather than JSX elements, createLucideIcon minimizes AST overhead while supporting full prop inheritance and TypeScript typings.

8. Lucide vs Tabler vs Feather vs Heroicons: 2026 Comparison

When selecting a core icon system for an enterprise web platform, evaluate the full technical landscape:

Feature Lucide Icons Heroicons (v2) Tabler Icons Feather Icons
Total Glyphs 1,490+ 1,176 5,200+ 287 (Frozen)
Default Grid 24x24 px 24px / 20px / 16px 24x24 px 24x24 px
Default Stroke 2.0 px 1.5 px (Outline) 2.0 px 2.0 px
Style Variants Outline Outline, Solid, Mini, Micro Outline, Filled Outline
License ISC / MIT MIT MIT MIT
Dynamic Loader Built-in Manual mapping Manual mapping None
shadcn/ui Default Yes No No No

For more detailed framework-level breakdowns, review our comparative benchmark: Lucide vs Tabler vs Phosphor Icons.

9. Browse & Export All 1,498 Lucide Icons on IconStash

While the lucide-react npm package is excellent for full-stack React projects, shipping an npm dependency is often unnecessary for lightweight landing pages, email templates, or Astro micro-sites.

On IconStash, all 1,498 Lucide Icons are indexed with instant search, live stroke adjustments, and one-click copy options:

  • Copy JSX / TSX: Copy ready-to-paste React components with clean SVG markup and zero dependency overhead.
  • Copy Raw SVG: Clean, SVGO-optimized vector markup with fill="none" stroke="currentColor".
  • Export High-Res PNG: Crystal-clear raster exports from 16px to 1024px.
  • Cross-Library Comparison: Compare any Lucide icon directly against identical concepts in Heroicons, Tabler, and Feather.

Frequently Asked Questions

What is the difference between Feather Icons and Lucide Icons?

Lucide is an active community fork of Feather Icons. Feather stopped receiving updates in 2020 with 287 icons, while Lucide has expanded to over 1,490 icons with identical 24x24 coordinate grids, 2px stroke weight, full TypeScript typings, and official packages for React, Vue, Svelte, and Angular.

Does importing from lucide-react bloat my production bundle?

Only if you use wildcard imports like 'import * as icons from lucide-react', which pulls in all 1,490+ icons (~1.2 MB unminified). When using standard named imports like 'import { Search, Heart } from lucide-react', modern bundlers like Vite, Turbopack, and Webpack 5 tree-shake the package so each icon adds only roughly 1.4 KB to your bundle.

How do I render dynamic Lucide icons from a database string without breaking tree-shaking?

Do not index into the full package namespace. Instead, use 'lucide-react/dynamicIconImports' combined with 'next/dynamic' or React.lazy to lazy-load only the requested icon on demand, or map against a predefined dictionary of allowable icon components in your application.

Can Lucide Icons be rendered inside Next.js React Server Components?

Yes. Lucide React components are pure functional components that emit standard SVG elements without state, context, or browser APIs. They render natively on the server in Next.js 15 App Router without needing the 'use client' directive, sending zero client-side JavaScript.

How do I change stroke width and size of Lucide icons using Tailwind CSS?

You can pass Tailwind utility classes directly via the className prop: 'className="size-6 stroke-[1.5] text-emerald-500"'. Lucide sets SVG stroke='currentColor' and strokeWidth='2' by default, which can be overridden via props (strokeWidth={1.5}) or via Tailwind CSS stroke width utilities.