Icons
DraftOur icon system is built for clarity and consistency. It aims to be bold, communicative, and functional, complementing typography and fitting naturally within every component.
- Usage
- Tokens
- Code
- Status & changelog
Common alternative names
Iconography, glyphs, pictograms, symbols
Icon set
Arch UI includes 474 icons from the @mdi/svg library, wrapped as typed React components in the @arch-ui/icons package. Each icon follows the naming pattern {Name}Icon — for example, SearchIcon, ChevronDownIcon, or AlertTriangleIcon.
The set covers five broad categories:
| Category | Examples |
|---|---|
| Navigation | ArrowUpIcon, ChevronLeftIcon, MenuIcon, OpenInNewIcon |
| Actions | PencilIcon, DeleteIcon, DownloadIcon, UploadIcon, ContentCopyIcon |
| Status | CheckCircleIcon, AlertIcon, AlertCircleIcon, InformationIcon |
| Objects | FileDocumentIcon, FolderIcon, ImageIcon, CalendarIcon, AccountIcon |
| Controls | PlusIcon, MinusIcon, CloseIcon, FilterIcon, MagnifyIcon |
Sizes
Arch UI supports three icon sizes. Each maps to a specific use case — do not scale icons to arbitrary pixel values.
| Size | Pixels | Use case |
|---|---|---|
size={16} | 16 × 16 | Inline with small text, compact UI, table cells |
size={20} | 20 × 20 | Default for form controls, buttons, input adornments |
size={24} | 24 × 24 | Navigation, page headers, standalone icon buttons |
All icons share a viewBox="0 0 24 24" and are scaled via the width and height attributes, so they remain crisp at every size.
Grid and alignment
All icons are drawn on a 24-unit grid with 2 units of internal padding, giving a 20-unit live area. Paths use filled shapes (no strokes) for consistent rendering across browsers and pixel densities.
When aligning icons with text, use align-items: center on a flex container. The icons are designed to optically center alongside text set in Inter at the corresponding size.
Accessibility
Decorative vs. meaningful icons
Every icon in Arch UI renders with aria-hidden="true" by default. This is correct when the icon accompanies visible text that already communicates the meaning. If the icon is the only element conveying information — for example, an icon-only button — you must provide an aria-label:
{/* Decorative — label is visible */}
<button>
<TrashIcon /> Delete
</button>
{/* Meaningful — icon is the only content */}
<button aria-label="Delete item">
<TrashIcon aria-label="Delete" />
</button>
When aria-label is provided, the component automatically sets role="img" and removes aria-hidden, making the icon visible to assistive technology.
Minimum touch target
Icon-only buttons must meet a 44 × 44 px minimum touch target (WCAG 2.5.8). The icon itself can be 16, 20, or 24 px, so pad the clickable area with spacing or a transparent border to reach the target size.
Do / Don't
<button aria-label="Close"><CloseIcon /></button>Wrap interactive icons in a <button> and provide aria-label when there is no visible text.
<CloseIcon onClick={close} />Don't attach onClick directly to the icon SVG or rely on a tooltip as the only accessible name.
<Icon size={20} />Use one of the three supported sizes (16, 20, 24). They are optimized for pixel-perfect rendering.
<Icon size={22} />Scaling icons to arbitrary values like 18 or 22 produces blurry edges.
color: var(--color-icon-default);Let icons inherit currentColor from the parent or pass a semantic color token.
color: #333;Hardcoded hex values bypass the color system and break in dark mode.
Component API
Every icon component accepts the same props through the shared IconProps interface:
| Prop | Type | Default | Description |
|---|---|---|---|
size | 16 | 20 | 24 | 24 | Icon dimensions in pixels |
color | string | "currentColor" | Fill color — use semantic tokens |
aria-label | string | undefined | Accessible label for standalone icons |
rtl | boolean | false | Flip horizontally in RTL layouts |
className | string | undefined | Additional CSS class |
style | CSSProperties | undefined | Inline style object (use sparingly) |
All standard SVG attributes are also forwarded via SVGProps<SVGSVGElement>.
Color
Icons do not define their own color tokens. They inherit from the surrounding text context through currentColor. This means an icon placed inside a disabled button automatically picks up the disabled text color without any extra props.
When you need to override color explicitly, pass a semantic token — never a raw hex value:
| Context | Recommended approach |
|---|---|
| Default content | Inherit from parent (currentColor) |
| Semantic meaning | color="var(--color-content-negative)" for errors, var(--color-content-positive) for success |
| Disabled state | Let the parent's disabled styling cascade |
| Inverted background | Inherit from a container using var(--color-content-inverse-primary) |
Spacing
When icons sit adjacent to text or other icons, use spacing tokens for the gap — not padding on the icon itself.
| Pairing | Recommended gap |
|---|---|
| Icon + label in a button | var(--spacing-8) (8 px) |
| Icon + body text inline | var(--spacing-4) (4 px) |
| Stacked icon buttons | var(--spacing-4) between targets |
| Icon within an input field | var(--spacing-12) from the field edge |
Full icon list
| Icon | Component name | Category |
|---|---|---|
InfoIcon | Status | |
AlertTriangleIcon | Status | |
WarningIcon | Status | |
CheckCircleIcon | Status | |
CheckIcon | Status | |
XCircleIcon | Status | |
SearchIcon | Controls | |
PlusIcon | Controls | |
MinusIcon | Controls | |
XIcon | Controls | |
FilterIcon | Controls | |
ArrowLeftIcon | Navigation | |
ArrowRightIcon | Navigation | |
ArrowUpIcon | Navigation | |
ArrowDownIcon | Navigation | |
ChevronDownIcon | Navigation | |
ChevronUpIcon | Navigation | |
ChevronRightIcon | Navigation | |
ChevronLeftIcon | Navigation | |
MenuIcon | Navigation | |
ExternalLinkIcon | Navigation | |
EditIcon | Actions | |
TrashIcon | Actions | |
DownloadIcon | Actions | |
UploadIcon | Actions | |
CopyIcon | Actions | |
FileIcon | Objects | |
FolderIcon | Objects | |
ImageIcon | Objects | |
CalendarIcon | Objects | |
UserIcon | Objects | |
UsersIcon | Objects | |
StarIcon | Objects | |
HeartIcon | Objects | |
BookmarkIcon | Objects | |
ClockIcon | Objects | |
EyeIcon | Objects | |
EyeOffIcon | Objects | |
LockIcon | Objects | |
UnlockIcon | Objects | |
SettingsIcon | Objects | |
MoreVerticalIcon | Controls | |
MoreHorizontalIcon | Controls |
Basic import
import { SearchIcon } from "@arch-ui/icons";
function SearchField() {
return (
<label>
<SearchIcon size={20} />
<input type="search" placeholder="Search..." />
</label>
);
}
Color inheritance
Because icons default to currentColor, wrapping an icon in a colored container is all you need:
<span style={{ color: "var(--color-content-negative)" }}>
<AlertTriangleIcon />
</span>
You can also pass the color prop directly when you need to break from the parent color:
<StarIcon color="var(--color-content-warning)" />
Icons alongside text
When an icon sits next to a label, use flexbox with a consistent gap. The icon should be vertically centered with the first line of text:
<button style={{ display: "flex", alignItems: "center", gap: "var(--spacing-8)" }}>
<DownloadIcon size={20} />
Download report
</button>
RTL support
Directional icons — arrows, chevrons, and similar — should flip in right-to-left layouts. Pass the rtl prop to opt in:
<ArrowRightIcon rtl />
The underlying CSS uses [dir="rtl"] .arch-icon--rtl { transform: scaleX(-1); } to handle the flip automatically.
Adding new icons
The icon set is auto-generated from @mdi/svg using the pnpm generate script in packages/icons. To add a new icon:
- Confirm the icon exists in the Material Design Icons library.
- Add the MDI name to the generation script at
packages/icons/scripts/generate.ts. - Run
pnpm generateinside the icons package. - Export the new component from
packages/icons/src/index.ts.
Do not edit generated icon files by hand — they will be overwritten on the next generation run.
Status & changelog coming soon.