Icon
Phosphor icon wrapper with size, weight, and motion affordances.
Status: stable. Group: display. Markdown version: /design-system/components/icon.md
When to use
Icon wraps the Phosphor icon set with the project's size ladder, motion affordances, and accessibility defaults. It accepts the name of an icon in the bundled registry (curated icons plus icons auto-discovered from JSX name literals by build:icons), as PascalCase like ShareNetwork or a slug like share-network. A name that is not in the registry renders nothing and logs a development-only console warning. A rendered icon stays decorative (aria-hidden) unless you pass ariaLabel, which switches it to role=img with an accessible name. Legacy FontAwesome-style names are aliased so older call sites keep working.
When not to use
- Do not pass both
ariaLabelanddecorative. With both set,decorativewins: the span isaria-hiddenand the label is dropped, so the name is never announced. Pass one or the other. - Do not import a Phosphor component directly from
@phosphor-icons/reactto get the same shape; you lose the alias map, the SR semantics, and the size ladder. Use Icon.
Look-alikes
- Button: Do not build icon-only buttons from a bare Icon; use Button with icon and accessibleName so focus, hit target and keyboard activation are owned by the right component.
Props
| Prop | Type | Default | Description | Required |
|---|---|---|---|---|
name | string | none | Phosphor icon name. Accepts PascalCase (e.g., "ShareNetwork") or slug ("share-network"). | Yes |
weight | "thin" | "light" | "regular" | "bold" | "fill" | "duotone" | none | Optional Phosphor weight override. | No |
legacyStyle | "thin" | "light" | "regular" | "duotone" | "solid" | "brands" | none | Legacy FontAwesome-style weight string kept for backward compatibility. | No |
size | "sm" | "md" | "lg" | "xl" | "xs" | "2xs" | "2xl" | md | A named size from the ladder (2xs to 2xl) or a number of pixels. The type also accepts a number; numeric sizes render at that pixel size without the tokenized size class. | No |
color | string | none | SVG color override; defaults to the inherited current color. | No |
rotate | 0 | 90 | 180 | 270 | none | Quarter-turn rotation applied to the glyph. | No |
flip | "horizontal" | "vertical" | "both" | none | Mirror the glyph along one or both axes. | No |
spin | boolean | none | Apply the continuous rotation animation. | No |
pulse | boolean | none | Apply the pulse animation when spin is not active. | No |
mirrored | boolean | none | Use Phosphor's horizontal mirroring. | No |
ariaLabel | string | none | Accessible name for a meaningful icon. | No |
decorative | boolean | none | Hide the icon from assistive technology. Defaults to true without ariaLabel; when true it wins over ariaLabel, which is then dropped. | No |
Variants
- Available sizes:
2xs,xs,sm,md,lg,xl,2xl(defaultmd). - Allowed
weightvalues:thin,light,regular,bold,fill,duotone. - Allowed
legacyStylevalues:thin,light,regular,duotone,solid,brands. - Allowed
rotatevalues:0,90,180,270. - Allowed
flipvalues:horizontal,vertical,both.
Invalid combinations
The contract records no invalid combinations.
Keyboard
The contract records no keyboard interaction.
Rules
Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.
| Level | Rule | How it is checked |
|---|---|---|
| MUST | ARIA: Decorative icons use aria-hidden. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Meaningful icons require aria-label. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by npm run test:stories:hc. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-11-04. |
| SHOULD | Keep icons decorative (no ariaLabel) inside labelled controls; the control's own label is the accessible name. | Not stated in the contract. |
| SHOULD | Pass ariaLabel when the icon is the only content of an interactive element, so the control has a name. | Not stated in the contract. |
| SHOULD | Use spin for loading and pulse for ambient activity; both stop under prefers-reduced-motion. | Not stated in the contract. |
| SHOULD | Pair semantic status icons with visible text; the icon reinforces, the text carries the meaning. | Not stated in the contract. |
| SHOULD NOT | Do not build icon-only buttons from a bare Icon; use Button with icon and accessibleName so focus, hit target and keyboard activation are owned by the right component. | Not stated in the contract. |
| SHOULD NOT | Do not pass both ariaLabel and decorative. With both set, decorative wins: the span is aria-hidden and the label is dropped, so the name is never announced. | Not stated in the contract. |
| SHOULD NOT | Do not force brand marks through Icon; anything outside the Phosphor catalogue goes through the image pipeline. | Not stated in the contract. |
Install and import
- Install:
npm install @digitaltableteur/react(version 0.9.1, restricted access on npm). - Import:
import { Icon } from "@digitaltableteur/react"; - Entry point:
@digitaltableteur/react/identityexports it too. - Styles: import
@digitaltableteur/react/style.cssonce at the app boundary. - Also available as the custom element
<dt-icon>; see the custom elements manifest.
Links
- Machine-readable contract (Design System Contract 1.0)
- Markdown version of this page
- Component index as JSON
- All components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.