Skip to main content
Amends 0165 (the UI contract, where icons are named by the declarations and drawn by the platform). It builds on the design-system category of 0302, which holds @stackra/icons beside @stackra/tokens and @stackra/ui. The page 0165 carries a note in this change (STA-414).
Amended 2026-10-24 by 0322: a declaration writes the generated constant, icon: Icons.House, where decision 1 has it write the string "house". The IconName union of decision 2 stays, as the type behind the constants, and gen still checks each name against it. The Icons.* form that this page rejects was an import of an icon component, and that stays rejected: a constant is a value that gen emits. Decisions 3 to 6 stand.Enforcement of this note: planned — gen emission of the constants and the lint rule of 0322 decision 6.

Context

Navigation entries, KPI tiles, banners and buttons all show an icon. The v3 proposals and the template imported an icon component into each declaration (import { Icons } from "@stackra/ui/icons", then icon: Icons.Receipt). That ties every declaration to one icon library and one renderer, and it puts a component import into a file that is otherwise data. A declaration is read by gen, by the manifest and by the web and native clients, and none of them needs a component to do so.

Decision

  1. A declaration references an icon only by its IconName, a string. No declaration imports an icon component. Examples are navigation: { group, icon: "receipt", order: 20 } and d.kpi("revenue").icon("banknote").
  2. IconName is a string union. It is generated from Lucide 1.54.0, which is pinned, and it adds the Stackra aliases. It is exported from @stackra/icons/contracts.
  3. @stackra/ui resolves a name to a drawing on each platform: lucide-react on the web and lucide-react-native on native. The map from names to drawings is the only place that knows the library. Swapping the library changes that map in @stackra/icons and nothing else.
  4. Gen validates every icon name used in a declaration against IconName, and emits a registry of the names that the build uses. A bundle holds the drawings of the used names only.
  5. A member’s own icons, in its assets/icons/ folder, join the set under the namespace form <namespace>::<name>, for example billing::invoice. They follow the asset rules of ADR 0234, and an override of one replaces the file whole (ADR 0306 rule 4).
  6. The package is @stackra/icons in the design-system category (ADR 0302). It has a contracts entry, which holds the name union, and a platform entry per renderer.

Consequences

  • A declaration is data that every target reads the same way. A reviewer sees the name and nothing else.
  • A name that does not exist fails at gen, so a typo does not render an empty square.
  • The icon library can change in one file, and the bundle size follows the names used.
  • The Icons.* form in the earlier proposals and the template is not used. The template is converted in the template pass (STA-344).

Enforcement

  • planned — gen validation of icon names against IconName, and the used-names registry. The work item is not yet open in Linear.
  • planned — the @stackra/icons package, which is not built yet (ADR 0302 places it, and its work item follows the design-system build).

Supersedes and sources

Supersedes: none. The icon component import of the earlier proposals and of the template is not a recorded decision, so nothing is superseded; it is replaced in the template by the rule above. Origin: the v3.1 design review of 2026-10-23, where the icon model was approved, restated in this ADR’s own words.