Icon browser
StableSearch all 161 icons by name or use, check them at 16 to 24 pixels, and copy one as SVG, JSX or its name.
IconGallery is the full browser: a search that understands uses (“delete” finds trash), category chips with live counts, a grid you move through with the arrow keys, and a detail panel. IconDetail and IconStage are its parts, and searchIcons and iconSvg are there for building your own picker.
The browser
import { IconGallery } from '@rchursin/product/iconset'
/** * The whole browser: search by name or use, filter by category, preview at 16, 20 or 24 on * either ground, then pick an icon to copy it. Press / to search, arrows to move in the grid. * The preview size and ground are remembered in this browser. On a page of its own, drop the * scrolling box: the gallery is meant to run full height with the page. */export default function IconGalleryExample() { return ( <div className="rc-scroll max-h-[min(50rem,85dvh)] overflow-y-auto p-4 sm:p-6"> <IconGallery /> </div> )}Usage
import { IconDetail, IconGallery, IconStage, iconSvg, searchIcons,} from '@rchursin/product/iconset'<IconGallery />
<IconDetail name="calendar" /><IconStage name="calendar" mode={16} />Examples
Icon details
IconDetail on its own: copy buttons, the drawing on its grid or as real pixels, both grounds, and the pixel-grid check.
import { useState } from 'react'import { IconDetail } from '@rchursin/product/iconset'import { Segmented } from '@rchursin/product/ui'
/** * IconDetail on its own: copy buttons, the drawing on its grid (or the real pixels at 16 and * 24), the icon at 1× on both grounds, and how its strokes sit on the pixel grid. */const picks = ['session', 'calendar', 'agent', 'status-review'] as const
export default function IconDetailExample() { const [name, setName] = useState<(typeof picks)[number]>('session') return ( <div className="flex w-full max-w-sm flex-col gap-4"> <Segmented label="Icon" size="sm" value={name} onChange={setName} options={picks.map((p) => ({ value: p, label: p }))} className="self-start" /> <div className="lux-card rounded-2xl bg-raised p-5"> <IconDetail name={name} /> </div> </div> )}Drawing and pixels
IconStage shows the construction grid, or what this browser really paints at 16 and 24 pixels, magnified.
import { IconStage, type StageMode } from '@rchursin/product/iconset'
/** * IconStage draws one icon large: on its construction grid ("vector"), or as the pixels this * browser really paints at 16 or 24, magnified. Each stage is an image with its own name. */const modes: { mode: StageMode; caption: string }[] = [ { mode: 'vector', caption: 'Drawing on the 24 grid' }, { mode: 16, caption: '16 px, one square per pixel' }, { mode: 24, caption: '24 px, one square per pixel' },]
export default function IconStageExample() { return ( <div className="grid w-full max-w-3xl grid-cols-1 gap-5 sm:grid-cols-3"> {modes.map(({ mode, caption }) => ( <figure key={mode} className="mx-auto flex w-full max-w-60 flex-col gap-2"> <div className="overflow-hidden rounded-xl ring-1 ring-line"> <IconStage name="session" mode={mode} keylines={mode === 'vector'} /> </div> <figcaption className="font-mono text-[11px] text-fg-3">{caption}</figcaption> </figure> ))} </div> )}Build your own picker
searchIcons finds icons by any word they are filed under; iconSvg returns the markup the kit ships.
import { useMemo, useState } from 'react'import { Icon, type IconName } from '@rchursin/product/icons/Icon'import { iconSvg, searchIcons } from '@rchursin/product/iconset'import { CodeBlock, Field, TextInput } from '@rchursin/product/ui'
/** * A picker of your own, from the gallery's two helpers: searchIcons() finds icons by the start * of any word in the name, keywords or category ("cal" finds calendar, not vertical), and * iconSvg() returns the exact markup the kit ships for one icon. */export default function IconSearchExample() { const [query, setQuery] = useState('date') const [picked, setPicked] = useState<IconName>('calendar') const results = useMemo(() => searchIcons(query).slice(0, 8), [query]) const shown = results.some((r) => r.name === picked) ? picked : results[0]?.name
return ( <div className="flex w-full max-w-lg flex-col gap-4"> <Field label="Find an icon" hint="Try a use as well as a name: delete, cost, date."> {(p) => <TextInput {...p} type="search" icon="search" autoComplete="off" spellCheck={false} value={query} onChange={(e) => setQuery(e.target.value)} />}API
IconDetail props
| Prop | Type | Default | Description |
|---|---|---|---|
namerequired | IconName | – | The icon to show. |
showHeader | boolean | true | The name (as an <h3>) and category above the buttons. Turn it off when a dialog title already says it. |
headingId | string | – | An id for that <h3>, so a surrounding region can be labelled by it. |
className | string | – | Extra classes on the root. |
IconStage props
| Prop | Type | Default | Description |
|---|---|---|---|
namerequired | IconName | – | The icon to draw. |
mode | 'vector' | 16 | 24 | 'vector' | The drawing on its 24 grid with anchors and centre lines, or the pixels this browser paints at 16 or 24, one square per pixel. |
keylines | boolean | true | The dashed circle, portrait and landscape keylines. |
pixelGrid | boolean | true | The dotted 0.75 + 1.5k pixel grid (vector mode only). |
className | string | – | Classes on the <svg>. It is a square that fills its width. |
Helpers
| Prop | Type | Default | Description |
|---|---|---|---|
searchIcons(query, list?) | (query: string, list?: readonly IconInfo[]) => IconInfo[] | – | Every typed word must start a word in the name, a keyword or the category, so “cal” finds calendar but not vertical. Exact names come first, then names that start with the query, then other name matches, then keyword and category matches, each group in the set’s order. An empty query returns the whole list. |
iconSvg(name) | (name: IconName) => string | – | The icon as one line of SVG markup, exactly as icons/svg/<name>.svg ships: style on the root, geometry in the children. |
iconJsx(name) | (name: IconName) => string | – | The JSX to paste, e.g. <Icon name="calendar" />. |
iconFile(name) | (name: IconName) => string | – | The file’s path in the kit, e.g. icons/svg/calendar.svg. |
geometryOf(name) | (name: IconName) => Geometry | – | Anchor points, circles, centre lines and which straight strokes sit on the 0.75 + 1.5k pixel grid. Measured once per icon, then cached. |
rasterize(key, nodes, size, opts?) | (key: string, nodes: IconNode, size: number, opts?: RasterOptions) => Uint8ClampedArray | null | – | How much ink (0 to 255) each pixel gets when this browser paints the drawing at size pixels, row by row; null without canvas. Cached by key, size and stroke. Browser only. |
useRaster(key, nodes, size, opts?) | (key: string, nodes: IconNode, size: number | null, opts?: RasterOptions) => Uint8ClampedArray | null | undefined | – | rasterize for rendering: undefined in the prerender and while the page hydrates (draw nothing yet), then the ink. IconStage and Construction use it. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| / | Jump to the search field and select its text (while focus is anywhere in the gallery, but not in a field). |
| Down arrow | From the search field: move into the results. |
| Enter | From the search field: open the first result. On an icon: open it. |
| Esc | In the search field: clear it. In the details dialog: close it. |
| Left arrowRight arrowUp arrowDown arrow | Move between icons. The grid is one Tab stop and remembers the last icon you were on. |
| HomeEnd | First or last icon in the row. |
| CtrlHomeCtrlEnd | First or last icon in the set. |
| Space | Open the focused icon. |
| Left arrowRight arrow | In the category chips and the size switch: choose the previous or next one (it wraps). |
- Search sits in a role="search" region with a hidden label and a hint that says how to reach the results. The result count is announced once you pause typing, not on every key.
- Categories are a radio group drawn as chips, each with its count read out (“Time, 21 icons”). Icons are buttons in a list with one Tab stop; the open one has aria-current.
- When the gallery is 720 pixels wide or more, choosing an icon announces “Showing calendar” and updates the side panel; narrower, it opens a modal dialog that hands focus back when it closes.
- IconStage is a single image with a name that says what it shows (“calendar at 16 pixels, magnified”). Layer switches are toggle buttons with aria-pressed.
- When the clipboard is blocked, the text appears in a selected field with a note to press Ctrl C or ⌘ C, so copying still works from the keyboard.
- The site search also listens for “/”, but while focus is inside the gallery its own “/” runs first (scoped shortcuts win over page-wide ones), so it focuses the gallery’s search.
Guidelines
Do
- Give the gallery the full width of a page. It measures itself: the side panel needs 720 pixels of gallery, whatever the window.
- Search by what you need it for. Keywords hold the uses, so “cost” finds dollar and “delete” finds trash.
- Check a new icon at 16 pixels in the stage before shipping it.
Don’t
- Don’t put two galleries on one page: they share the saved preview size and Inverse switch, so changing one changes both.
- Don’t copy the SVG markup and then restyle the stroke; change the size and colour with classes instead.
In use
Related
- IconOne component for all 161 icons: a 24-pixel grid, a 1.5 stroke, square ends, coloured by the text around it.
- Icon guidelinesHow the icons are built, how status and priority are drawn, how to use icons, and how the set ships, as live sections.
- Copy & downloadCopy text with a toast and a fallback when the clipboard is blocked, and save text as a file.
- Segmented controlA radio group drawn as one strip, with a lit pill that glides to the choice and arrow keys that move and choose.
- DialogA modal on the native <dialog> element: the page behind goes inert, Esc closes it, and focus goes back to what opened it.
Source
The component’s own files, exactly as they run on this site.
/** * The icon gallery: search by name or meaning, filter by category, preview at 16 / 20 / 24 on * either ground, and pick an icon to see its construction and copy it. * * Keyboard: "/" jumps to search (while focus is anywhere in the gallery). In the search field, * ↓ moves into the grid, Enter opens the first result, Esc clears. In the grid, arrow keys move * (one tab stop for the whole grid), Home / End go to the ends of the row, Ctrl+Home / Ctrl+End * to the ends of the set, Enter or Space opens the icon. * When the gallery itself is wide enough (720px, whatever the window) the details sit in a sticky * side panel; narrower, in a docs column or on a phone, they open in a dialog. */import { useEffect, useId, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type RefObject,