Iconography
Phosphor icons, sized by the component they sit in rather than by the icon.
Every icon in the top row gets its size from the Button around it through data-icon. The row underneath sets size-* on the icon directly, which is what you'd do outside a component.
Library
The icon set is Phosphor. components.json sets iconLibrary to phosphor, and every component that draws an icon imports it from @phosphor-icons/react: XIcon in the dialog close button, CaretDownIcon and CheckIcon in select, CaretRightIcon in dropdown submenus, ArrowLeftIcon and ArrowRightIcon in the carousel, CircleNotchIcon for Spinner. I use the default weight throughout; nothing in the components sets weight.
Sizing comes from the parent
An icon doesn't decide its own size. The component it sits in does, with a descendant selector:
Buttonsizes anything carryingdata-icon:[&_[data-icon]]:size-4 [&_[data-icon]]:shrink-0. Plain svgs without the attribute are left alone.Marker,MarkerIconandInputGroupuse[&_svg:not([class*='size-'])]:size-4, so an explicitsize-*on the icon still wins.Selectsets[&_svg]:size-4on its trigger and items.AlertDialogMediauses the same:not([class*='size-'])pattern atsize-6.
data-icon is currently a presence check. The carousel passes data-icon="inline-start" and the dialog passes a bare data-icon, and Button treats them the same: there is no inline-start or inline-end spacing rule yet. If I add one, the values will be inline-start and inline-end.
Colour
Icons use currentColor, so they follow the text token of whatever they're in. Dimming is done with opacity, as in the select trigger's opacity-50 caret, rather than with a separate icon colour.
Meaning
Decorative icons next to text need nothing extra. MarkerIcon sets aria-hidden="true". Icon-only controls always get a label: the dialog close button has <span className="sr-only">Close</span>, the carousel buttons say "Previous slide" and "Next slide", and Spinner renders with role="status" and aria-label="Loading".
Rules
The same rules ship in DESIGN.md and the MCP server, for the agents building with this.
- must
Use icons from
@phosphor-icons/react, imported by theirIcon-suffixed names such asXIconandCaretDownIcon.use-phosphor
- should
Put
data-iconon an icon inside aButtonso the button's[&_[data-icon]]:size-4 [&_[data-icon]]:shrink-0rule sizes it.mark-icons-in-buttons
- should
Leave out a
size-*class on an icon unless you need a different size; containers likeMarkerandInputGroupapplysize-4to any svg without one.let-the-parent-size
- must
Give every icon-only control a text label, as a
<span className="sr-only">oraria-label, because the icon itself carries no name.label-icon-only-controls