Icons by name, safe by default — store a string, render an icon.
Most of the time you import the icon you want straight from
@phosphor-icons/react and render it. This package is for the other case: the
icon name lives in your data, chosen by a user or stored against a category,
so you only know it at runtime. Instead of hand-rolling a lookup map that goes
stale (and takes the page down the first time a name in the database does
not match it), you own one component that resolves any Phosphor name and
fails soft.
The last row is the point: "NotAnIconName" renders a fallback, not a crash.
Install
bun add @voila.dev/ui@import "@voila.dev/ui/styles/sources.css";Or copy the source — it's the same files.
Peers: react@19, react-dom@19, tailwindcss@4.
The 3-minute win
Categories in the database store an icon name; the UI renders whatever is there:
import { Icon, type PhosphorIconName, phosphorIconNames } from "@voila.dev/ui/icon";
const categories = [
{ name: "Design", icon: "PenNibIcon" },
{ name: "Development", icon: "CodeIcon" },
{ name: "Copywriting", icon: "TextTIcon" },
];
export function CategoryList() {
return (
<ul className="flex flex-col gap-2">
{categories.map((category) => (
<li key={category.name} className="flex items-center gap-2 text-sm">
<Icon name={category.icon} className="size-5" />
{category.name}
</li>
))}
</ul>
);
}A renamed or mistyped icon in the data falls back to TagIcon instead of
throwing, and in development it logs a warning naming the missing icon.
Mental model
The icon is data, the component is the lookup. Icon takes any string,
resolves it against the full Phosphor set at runtime, and passes everything
else (weight, size, color) through to the underlying Phosphor component.
That is also why the package pulls in the whole set: any name has to resolve.
Keep it out of bundles that only need a fixed handful of icons; import those
directly from @phosphor-icons/react instead.
The list of valid names ships as an exported array and a union type, so you can validate input or build a picker without maintaining your own list:
import { type PhosphorIconName, phosphorIconNames } from "@voila.dev/ui/icon";Page map
- Icon: the component, its fallback behaviour, weights and accessibility.
For letting a user choose an icon, @voila.dev/ui ships an
IconPicker built on top of this package.