ADRs
ADR 0047 — Foundation icons sourced from Phosphor (amends ADR 0036 §4 / ADR 0046)
  • Date: 2026-06-06
  • Status: Accepted
  • Phase / Feature: Foundation v1 · icons
  • Amends: ADR 0036 §4 ("100 icons custom-designed"), ADR 0046 §1/§2 ("v1.x custom-designed → 100")
  • Closes: T7.2 + T7.3 design pass (now realized by import, not in-house drawing)

Context

ADR 0036 §4 promised 100 custom-designed icons across 14 categories, with Solid/Line variants and RTL-aware flags. ADR 0046 §3 sequenced the design pass priority list. T7.4 shipped the per-icon pipeline that any source-of-truth can feed.

After shipping the pipeline + the spec, the design pass itself stayed parked — the work needs a designer sitting in Figma drawing 200+ SVGs with optical alignment + variant pairing. ARNO ships without that pass today.

Two paths to unblock:

  1. Wait for the design pass. Foundation stays at 7 essentials until a designer cycles through ~100 icon × 2 variant × RTL review.
  2. Import from a community icon library. Trade visual-identity custom intent for ship velocity. Designer can replace specific icons later via the ADR 0041 brand_icon upload flow.

This ADR picks path 2 for v1, names Phosphor as the source, and sequences the eventual override path. ADR 0036 §4's "custom-designed" promise is amended to "sourced today, replaceable per project".

Decision

§1 — Phosphor over Lucide / Heroicons / Feather

Phosphor (https://phosphoricons.com (opens in a new tab), MIT license) chosen for three reasons:

  • Paired variants ship together. Phosphor's regular (outline) + fill (solid) weights cover ADR 0036 §4's Solid/Line promise in one library, in one consistent style. Lucide is outline-only — adopting Lucide would force us back to designing solid weights ourselves. Heroicons ships both but its visual language (Apple-derivative) clashes with the rest of Foundation.
  • Wider category coverage. Phosphor ships 1200+ icons across 6 weights; ADR 0046 §3 priority list is fully covered by Phosphor's catalog. No category gaps require fallback design.
  • MIT license + clean attribution. No restrictive clauses, no reserved name (unlike SIL OFL fonts). Required attribution lives in packages/foundation/icons/LICENSE-PHOSPHOR.md.

§2 — Import 100 (actually 108) icons mapped to ADR 0046 §3 priority categories

packages/foundation/build-foundation-icons.mjs is the single source of truth. The script lists every Foundation icon as a triple { arno: <Foundation name>, phosphor: <Phosphor name>, rtl: 'flip' | 'keep' }, reads Phosphor's regular + fill SVG markup, and codegen-emits one .tsx per icon + an updated Icon.tsx catch-all REGISTRY + an updated sprite generator.

Category coverage (per ADR 0046 §3):

  • Essentials (7) — check / x / chevron-down / person / alert / info / error. Replace the prior hand-drawn versions so all 100 share the same visual language.
  • Directional (13) — chevrons + arrows + corners. RTL flag set per direction (left/right arrows flip; up/down stay).
  • Action verbs (28) — copy/edit/save/trash/plus/minus/settings/search/filter/etc.
  • Status + feedback (10) — bell/alert-circle/check-circle/help/zap/sun/moon/etc.
  • File / format (10) — file/folder/image/cloud/etc.
  • Communication (7) — mail/chat/phone/send/etc.
  • User / identity (6) — users/shield/crown/user-plus/etc.
  • Time (3) — calendar/clock/history.
  • Layout / structure (8) — grid/list/sidebar/columns/etc.
  • Tools / misc (16) — code/terminal/globe/flag/etc.

Total: 108 (originally planned 100; landed slightly higher because Phosphor's catalog naturally covered a few neighboring concepts cheaply).

§3 — Codegen is the contract

The script is the source of truth. Editing a generated .tsx by hand will be overwritten next run. To change an icon: edit ICONS in build-foundation-icons.mjs, re-run node build-foundation-icons.mjs, commit the diff.

Generator outputs:

  • packages/foundation/src/icons/<Name>Icon.tsx per icon
  • packages/foundation/src/icons/index.ts re-exports
  • packages/foundation/src/components/Icon.tsx IconName union + REGISTRY
  • packages/foundation/build-icons.mjs sprite generator (regenerated with current path data)

The _shared.tsx IconBase + RTL behavior (ADR 0040) remain unchanged; viewBox bumped from 0 0 24 24 to 0 0 256 256 to match Phosphor's coordinate system.

§4 — Per-project override path

A designer who wants Foundation's check to look different in their project uploads a replacement via the ADR 0041 brand_icon flow under the same name. Render precedence in the eventual editor surface picks brand over Foundation when both exist (ADR 0041 §"Open questions" — same answer).

This is the "custom-designed at the seam, sourced in the middle" pattern. Foundation ships a coherent default; the brand layer carries identity.

§5 — Replacement / upgrade path

If a future custom-designed in-house pass ships (Foundation v2 §6 priority), the same generator script can swap source from Phosphor to the internal design system in one edit. Per-icon API + sprite IDs stay stable across the swap; consumers don't break.

Anti-patterns explicit

  • Do not hand-edit any generated .tsx file. The next regen overwrites your work + the contract is documented at the top of each file.
  • Do not import other Phosphor weights (thin/light/bold/duotone) at the Foundation layer. Solid + Line is the API contract per ADR 0036 §4; offering more weights is a v2 conversation, not a "while we're here" addition.
  • Do not mix sources mid-set (some icons Phosphor, others Lucide). Visual coherence over per-icon-best-pick.
  • Do not ship icons that don't have both regular + fill in Phosphor. If a needed concept lacks a fill variant, the brand-icon upload flow is the answer.

Open questions / parking

  • Custom in-house pass for select icons (brand-defining marks like the logo or product-specific glyphs) — parked behind ADR 0046 v2 work + the brand_icon upload route.
  • Phosphor version bumps. When Phosphor releases a new major (path-data changes), re-running the generator updates the set. Bumping is opt-in; the lockfile pins the current version.
  • Tree-shaking the import-time devDep. @phosphor-icons/core is only used at codegen time and lives in devDependencies. Runtime bundle has no Phosphor dependency.