lucide-icon-picker
v1.0.1
Published
Flexible, composable React icon picker for Lucide icons. No extra UI library required.
Downloads
219
Maintainers
Readme
- Search across 1500+ Lucide icons with live filtering
- Paginated icon grid — no lag, no virtual-scroll dependency
- Optional color picker, size slider, and stroke width slider — all hidden by default
- Live preview strip inside the panel before confirming selection
- Auto-flip placement — repositions when near the viewport edge
- Mobile drawer mode — slides up from the bottom on narrow screens
- Headless mode — plug in your own popup component
<RenderIcon />renders any icon by name safely — returnsnullfor unknown names, never crashes- Zero runtime dependencies beyond React and lucide-react
- Full CSS variable system for theming without overrides
- Works with CJS and ESM bundlers
npm install lucide-icon-pickerPeer dependencies — install if not already present:
npm install react react-dom lucide-reactImport the stylesheet once in your app entry point:
import 'lucide-icon-picker/dist/lucide-icon-picker.css';Quick Start
The simplest possible usage — three lines:
import IconPicker from 'lucide-icon-picker';
import { useState } from 'react';
function App() {
const [icon, setIcon] = useState('Star');
return <IconPicker value={icon} onChange={setIcon} />;
}Click the trigger, search, pick. Done.
Render an Icon Anywhere
import { RenderIcon } from 'lucide-icon-picker';
// Renders the icon by name — no configuration needed
<RenderIcon name="Star" />
// With custom appearance
<RenderIcon name="Heart" color="#e11d48" size={32} stroke={1.5} />
// Safe with any input — returns null instead of crashing
<RenderIcon name={untrustedUserInput} size={20} />stroke is a short alias for strokeWidth. Both work.
With Optional Sections
import IconPicker from 'lucide-icon-picker';
function App() {
const [config, setConfig] = useState({
name: 'Star',
color: '#3b82f6',
size: 24,
strokeWidth: 2,
});
return (
<IconPicker
value={config.name}
onChange={(name, meta) =>
setConfig(prev => ({ ...prev, name, ...meta }))
}
colorPicker
sizeControl
strokeControl
defaultColor={config.color}
defaultSize={config.size}
defaultStroke={config.strokeWidth}
placement="bottomLeft"
/>
);
}onChange receives (name, { color, size, strokeWidth }).
Headless Mode — Bring Your Own Popup
import { useIconPicker, IconPickerPanel, RenderIcon } from 'lucide-icon-picker';
function MyPicker({ value, onChange }) {
const { open, setOpen, ...pickerProps } = useIconPicker({ value, onChange });
return (
<YourPopover
open={open}
onOpenChange={setOpen}
content={<IconPickerPanel {...pickerProps} colorPicker />}
>
<button onClick={() => setOpen(v => !v)}>
<RenderIcon name={value} size={16} />
{value ?? 'Pick icon'}
</button>
</YourPopover>
);
}Programmatic Control via Ref
const ref = useRef(null);
<IconPicker ref={ref} value={icon} onChange={setIcon} />
ref.current.open();
ref.current.close();
ref.current.focus();Non-JSX Contexts
For props that expect a React element rather than a component:
import { renderIcon } from 'lucide-icon-picker';
<Avatar icon={renderIcon('User', { size: 20 })} />Props — IconPicker
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Controlled icon name (PascalCase) |
| onChange | (name, meta) => void | — | Called on selection |
| colorPicker | boolean | false | Show color section |
| sizeControl | boolean | false | Show size slider |
| strokeControl | boolean | false | Show stroke width slider |
| defaultColor | string | #000000 | Starting color |
| defaultSize | number | 24 | Starting size in px |
| defaultStroke | number | 2 | Starting stroke width |
| placement | string | bottomLeft | bottomLeft bottomRight bottom topLeft topRight top left right |
| panelWidth | number | 288 | Panel width in px |
| gridColumns | number | 8 | Icons per row |
| pageSize | number | 32 | Icons per page |
| mobileMode | string | drawer | drawer panel none |
| trigger | ReactNode | — | Replace the default trigger with your own element |
| triggerClassName | string | — | Extra class on the default trigger button |
| disabled | boolean | false | Disables the picker |
| className | string | — | Class on the outer wrapper |
Props — RenderIcon
| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | required | Lucide icon name in PascalCase |
| color | string | #000000 | #hex rgb() hsl() |
| size | number | 24 | px — clamped 8–128 |
| strokeWidth | number | 2 | Clamped 0.5–4 |
| stroke | number | — | Alias for strokeWidth |
CSS Customisation
Every visual detail is a CSS variable. Set them on .lip-wrapper or any ancestor:
/* Purple theme */
.lip-wrapper {
--lip-primary: #7c3aed;
--lip-primary-bg: #f3e8ff;
}
/* Wider panel, more columns */
.lip-wrapper {
--lip-panel-width: 360px;
--lip-grid-columns: 10;
}
/* Higher stacking order */
.lip-wrapper {
--lip-z-index: 9999;
}All variables:
| Variable | Default | Controls |
|---|---|---|
| --lip-primary | #3b82f6 | Accent — selected cell, focus ring, sliders |
| --lip-primary-bg | #eff6ff | Selected cell background |
| --lip-panel-width | 288px | Panel width |
| --lip-grid-columns | 8 | Icon columns in grid |
| --lip-panel-radius | 8px | Panel corner radius |
| --lip-z-index | 1050 | Panel stacking order |
| --lip-border | rgba(0,0,0,.10) | All borders |
| --lip-text | #111827 | Primary text |
| --lip-text-muted | #9ca3af | Labels and secondary text |
| --lip-bg | #ffffff | Panel and trigger background |
| --lip-bg-hover | #f3f4f6 | Icon cell hover state |
| --lip-shadow | — | Panel drop shadow |
Target any .lip- class directly for surgical changes:
/* Circular selected cells */
.lip-grid__item--selected { border-radius: 50%; }
/* Square trigger */
.lip-trigger { border-radius: 0; }
/* Taller pagination buttons */
.lip-pagination__btn { height: 34px; }| Platform | Support | Notes |
|---|---|---|
| React web | Supported | React 17 and 18 |
| Next.js | Supported | App Router and Pages Router |
| Remix | Supported | SSR-safe — DOM APIs are inside useEffect |
| Vite + React | Supported | Recommended setup |
| Create React App | Supported | |
| Gatsby | Supported | Use dynamic import if needed |
| Storybook | Supported | Import the CSS in .storybook/preview.js |
| Platform | Reason |
|---|---|
| React Native | This package uses lucide-react which renders SVG via the DOM. React Native uses lucide-react-native with a different rendering model — the two are incompatible. |
| Expo | Same reason as React Native. |
Version Compatibility
| lucide-icon-picker | lucide-react |
|---|---|
| 1.x | >= 0.400.0 — tracks latest |
Icons added in newer versions of lucide-react appear automatically.
Icons removed return null from <RenderIcon /> — nothing crashes.
License
MIT
