PVR Tech Studio

Icons Browser

An interactive browser for Luminaux's full lucide-react icon set at /icons — live search, size and stroke-width previews, pagination, and click-to-copy of any icon's component name.

5 min read
Updated July 15, 2026

Overview

Luminaux's icon system is lucide-react — every icon in the app (sidebar, header, widgets, buttons) is a lucide component. The Icons page (/icons, sidebar Components → Icons, lazy-loaded) is the reference browser: one Panel containing a toolbar (search input, Size S/M/L and Stroke 1.5/2/2.5 segmented controls, a live result count) over a responsive auto-fill grid of icon tiles, with a Pagination control at 96 icons per page.

The catalog is built once at module scope by enumerating the lucide-react exports: entries must start with an uppercase letter, names ending in Icon and known non-icon exports (Icon, LucideIcon, createLucideIcon, default) are skipped, and aliases are de-duplicated by component identity (alias exports share one object, so each glyph appears once), then sorted alphabetically.

Clicking a tile copies the icon's component name (e.g. ArrowUpRight) to the clipboard via navigator.clipboard.writeText, fires a success toast, and shows a transient "copied" overlay (check mark + name) on the tile for ~1.2 s — paste the name straight into an import. Search is a case-insensitive substring filter on the name and resets to page 1; there is no category filtering, search is the only filter. All page text lives in the demo i18n namespace (icons* keys), and every color is a semantic token (tiles tint primary on hover and while copied).

Architecture & files

FileResponsibility
src/pages/ui/IconsPage.tsxThe whole page — the module-scope ICONS catalog builder, the local Segmented pill control, search/page/size/stroke state, and the copy logic.
src/routes.tsxRegisters the lazy route: {path: '/icons', element: <IconsPage />}.
src/data/menu.tsThe sidebar leaf (key: 'icons', to: '/icons') under the Components section.
src/locales/en/demo.jsonThe icons* keys — iconsPanelTitle, iconsPanelSubtitle, iconsSearch, iconsSize, iconsStroke, iconsResults, iconsCopied, iconsNone (en + ja).

Usage

Navigate to /icons, search, tune size/stroke, click an icon, and paste its name into code:

import {ArrowUpRight} from 'lucide-react'
 
<ArrowUpRight className="h-4 w-4" />

Icons are ordinary React components — size them with Tailwind (h-4 w-4) or the size prop, and color them with semantic token utilities (text-primary, text-muted-foreground), never hex.

API / Props

IconsPage takes no props — it is a self-contained route screen. Its internals:

PieceWhat it does
ICONSModule-scope IconEntry[] ({name, Comp}) — the filtered, de-duped, sorted lucide catalog described above. Built once, not per render.
PER_PAGE96 — page size for the Pagination control.
SIZES20 / 24 / 30 px (labelled S / M / L) — applied live via each icon's size prop.
STROKES1.5 / 2 / 2.5 — applied live via strokeWidth.
SegmentedA local compact pill control (label + option buttons) driving the size/stroke state.
copy(name)Writes the name to the clipboard, toasts demo:iconsCopied, and flags the tile copied for 1.2 s (a single shared timeout).

Configuration & customization

  • Page size: change PER_PAGE in src/pages/ui/IconsPage.tsx.
  • Preview sizes/strokes: edit the SIZES / STROKES tables — the segmented controls render from them.
  • Copy format: copy() writes the bare component name. To copy a JSX snippet instead, change the writeText argument (e.g. `<${name} className="h-4 w-4" />`).
  • Catalog rules: the exclusion set (NON_ICON) and the Icon-suffix/uppercase checks live next to ICONS — adjust there if a future lucide release changes its export shape.
  • Copy is i18n — edit the icons* keys in src/locales/<lng>/demo.json, not the component.

Examples

Reusing the catalog idea elsewhere (e.g. an icon picker in a form):

import * as Lucide from 'lucide-react'
import type {LucideIcon} from 'lucide-react'
 
const NON_ICON = new Set(['Icon', 'LucideIcon', 'createLucideIcon', 'default'])
const seen = new Set<unknown>()
const icons = Object.entries(Lucide)
    .filter(([name, value]) => {
        if (!/^[A-Z]/.test(name) || name.endsWith('Icon') || NON_ICON.has(name)) return false
        if (typeof value !== 'object' && typeof value !== 'function') return false
        if (seen.has(value)) return false
        seen.add(value)
        return true
    })
    .map(([name, Comp]) => ({name, Comp: Comp as LucideIcon}))

The identity de-dupe matters: lucide exports many aliases (the same component under several names), and without it the grid would show duplicate glyphs.

Best practices

  • Import icons by name (import {Search} from 'lucide-react') in app code — tree-shaking keeps the bundle lean. The namespace import (* as Lucide) is appropriate only for a browser page like this one, which is why the route is lazy — the full icon set splits into its own chunk instead of the main bundle.
  • Color via tokens (text-primary, text-muted-foreground) so icons follow theme and skin.
  • Match the app's default stroke (2) and sizes (h-4 w-4 / h-5 w-5) unless a design calls for otherwise; the page's size/stroke controls exist to preview those choices.

Troubleshooting

SymptomCause / fix
Clicking a tile doesn't copynavigator.clipboard requires a secure context (https or localhost). The call is optional-chained, so the toast still fires — verify the context.
An icon you saw on lucide.dev is missingThe grid lists each component once under its canonical export name; aliases are de-duped. Search for another spelling of the name.
Search shows "No icons match"The filter is a substring match on the component name (PascalCase, no spaces) — try a shorter fragment (arrow, chart).
The page feels heavy to openIt imports the entire lucide set by design; it is lazy-loaded so the cost is contained to /icons. Don't copy the * as Lucide import into shared code.

FAQ

What exactly is copied? The icon's React component name (e.g. CalendarDays) — ready for a lucide-react named import.

Is there category filtering? No — the browser is search + pagination only. Lucide's site offers categories; here the name search covers the practical lookup path.

How many icons are there? The full installed lucide-react set (the live count renders next to the toolbar via demo:iconsResults). It grows with dependency updates automatically — no manifest to maintain.

Can I add a custom icon set? Luminaux standardizes on lucide. For one-off art, use inline SVGs with token vars (the empty-state illustration pattern) rather than mixing icon fonts or other packs.

Notes for designers & content editors

  • Page copy lives under the icons* keys in src/locales/<lng>/demo.json (en + ja seeded).
  • Tiles, hover tints, and the copied overlay are all token-driven (primary accents) — they re-skin and dark-mode automatically.
  • Use the size/stroke controls to preview how a glyph reads at the app's actual sizes before choosing it for navigation or buttons.

Was this page helpful?