Skip to main content

Design system components

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 ariaLabel and decorative. With both set, decorative wins: the span is aria-hidden and 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/react to 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

Icon props
PropTypeDefaultDescriptionRequired
namestringnonePhosphor icon name. Accepts PascalCase (e.g., "ShareNetwork") or slug ("share-network").Yes
weight"thin" | "light" | "regular" | "bold" | "fill" | "duotone"noneOptional Phosphor weight override.No
legacyStyle"thin" | "light" | "regular" | "duotone" | "solid" | "brands"noneLegacy FontAwesome-style weight string kept for backward compatibility.No
size"sm" | "md" | "lg" | "xl" | "xs" | "2xs" | "2xl"mdA 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
colorstringnoneSVG color override; defaults to the inherited current color.No
rotate0 | 90 | 180 | 270noneQuarter-turn rotation applied to the glyph.No
flip"horizontal" | "vertical" | "both"noneMirror the glyph along one or both axes.No
spinbooleannoneApply the continuous rotation animation.No
pulsebooleannoneApply the pulse animation when spin is not active.No
mirroredbooleannoneUse Phosphor's horizontal mirroring.No
ariaLabelstringnoneAccessible name for a meaningful icon.No
decorativebooleannoneHide 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 (default md).
  • Allowed weight values: thin, light, regular, bold, fill, duotone.
  • Allowed legacyStyle values: thin, light, regular, duotone, solid, brands.
  • Allowed rotate values: 0, 90, 180, 270.
  • Allowed flip values: 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.

Icon rules
LevelRuleHow it is checked
MUSTARIA: Decorative icons use aria-hidden.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Meaningful icons require aria-label.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTThe 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.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDKeep icons decorative (no ariaLabel) inside labelled controls; the control's own label is the accessible name.Not stated in the contract.
SHOULDPass ariaLabel when the icon is the only content of an interactive element, so the control has a name.Not stated in the contract.
SHOULDUse spin for loading and pulse for ambient activity; both stop under prefers-reduced-motion.Not stated in the contract.
SHOULDPair semantic status icons with visible text; the icon reinforces, the text carries the meaning.Not stated in the contract.
SHOULD NOTDo 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 NOTDo 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 NOTDo 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/identity exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-icon>; see the custom elements manifest.