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.
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
| File | Responsibility |
|---|---|
src/pages/ui/IconsPage.tsx | The whole page — the module-scope ICONS catalog builder, the local Segmented pill control, search/page/size/stroke state, and the copy logic. |
src/routes.tsx | Registers the lazy route: {path: '/icons', element: <IconsPage />}. |
src/data/menu.ts | The sidebar leaf (key: 'icons', to: '/icons') under the Components section. |
src/locales/en/demo.json | The 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:
| Piece | What it does |
|---|---|
ICONS | Module-scope IconEntry[] ({name, Comp}) — the filtered, de-duped, sorted lucide catalog described above. Built once, not per render. |
PER_PAGE | 96 — page size for the Pagination control. |
SIZES | 20 / 24 / 30 px (labelled S / M / L) — applied live via each icon's size prop. |
STROKES | 1.5 / 2 / 2.5 — applied live via strokeWidth. |
Segmented | A 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_PAGEinsrc/pages/ui/IconsPage.tsx. - Preview sizes/strokes: edit the
SIZES/STROKEStables — the segmented controls render from them. - Copy format:
copy()writes the bare component name. To copy a JSX snippet instead, change thewriteTextargument (e.g.`<${name} className="h-4 w-4" />`). - Catalog rules: the exclusion set (
NON_ICON) and theIcon-suffix/uppercase checks live next toICONS— adjust there if a future lucide release changes its export shape. - Copy is i18n — edit the
icons*keys insrc/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
| Symptom | Cause / fix |
|---|---|
| Clicking a tile doesn't copy | navigator.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 missing | The 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 open | It 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 insrc/locales/<lng>/demo.json(en + ja seeded). - Tiles, hover tints, and the copied overlay are all token-driven (
primaryaccents) — 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.
Related
Widgets
Heavy consumers of lucide icons (icon: LucideIcon props throughout).
Core & Feedback
Panel, Input, Pagination and Toast used by this page.
Animation & Effects
The Stagger entrance the page uses.
CSS System
The token utilities icons should be colored with.
Internationalization
The demo namespace conventions.
Was this page helpful?
