---
title: "Icon inventory"
description: "The generated list of every semantic icon role in the system and the lucide glyph it currently resolves to, so a swap is one mapping rather than a search."
url: "https://opsinjs.pensievelabs.org/foundations/iconography/icon-inventory"
source: "https://opsinjs.pensievelabs.org/foundations/iconography/icon-inventory.md"
section: "Foundations"
kind: "foundation"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["icon list", "icon roles", "available icons"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="foundation" />

## Overview [#overview]

Most design systems cannot answer the question "which icons does this system rely
on?" The glyphs are imported one by one in whichever component happens to need
them, and the only way to find out is to grep.

opsinjs answers it for the half of the set that is mapped, and this page is
where the rest is specified. The design is that a component asks for the icon
for *status: urgent*, for *category: sleep*, for *action: dismiss*. Each of
those is a **role**, and a single mapping resolves each role to a lucide name.

<NoDataYet what="The generated role-to-glyph table" script="scripts/build-registry.mts">
  No generator emits icon data yet: `scripts/build-registry.mts` reads the
  catalogue and the component sources and has no icon step, so running it will not
  fill this table. What exists instead is described below, in prose, with the file
  that holds it.
</NoDataYet>

**The status half of the mapping is live.** `CLINICAL_STATUS_META[level].icon`
in `lib/status.ts` names the lucide glyph for each of the four levels and for
`unknown`, and `lib/status.ts` ships inside every implemented registry
item. `StatusPill`, `RangeBar` and `ScoreDial` each keep a local binding from
that name to a component and assert in development that the two agree, so the
four shapes cannot drift apart from the four words.

**The other halves are not mapped.** Category, data-state, action and provenance
roles have no table, and eight built components import their glyphs from lucide
directly as a result. That is a debt this page records, not a design: a direct
import is invisible to this table, to the contrast audit and to anybody swapping
the icon set.

What is fixed now is the shape of the whole set, and the shape is what makes the
rest possible: swapping a glyph is one line, auditing the whole set is one table,
and a product that wants its own icon library replaces the mapping rather than
editing components.

The browsable version, where you can search and copy, is at
[/icons](/icons); this page is the reference form of the same data.

## How it works [#how-it-works]

**Roles are grouped by what they do**, and the grouping is not cosmetic. Each
group has different rules.

*Category roles.* One per health category. These are semantic: the glyph is
carrying which part of someone's health a surface is about, because
[colour cannot](../../health/two-colour-axes.mdx). They are the roles most likely
to be customised by a product with its own categories, and the mapping is where
that happens. See
[Theming → Category palettes](../../theming/category-palettes.mdx).

*Status roles.* One per clinical status level. Always accompanied by colour and a
word; never used alone. The set is deliberately closed, because an extra status
glyph is an extra status level that the system does not have.

*Data-state roles.* Empty, error and stale each have a glyph. Loading does not,
because a skeleton is not an icon.

*Action roles.* Dismiss, expand, add, edit, back, more. The mundane majority.

*Provenance roles.* Where a reading came from: entered by hand, from a device,
imported. These matter more than they look. See
[Data provenance and device accuracy](../../health/data-provenance-and-device-accuracy.mdx).

**Each row carries four things**: the role, the lucide glyph it resolves to, what
it is used by, and its default accessible name where it can appear alone. The
fourth column is the one that stops icon-only controls shipping unnamed.

**Where the set is constrained.** Everything on
[Health glyphs](./health-glyphs.mdx) is excluded from the mapping by rule, not by
convention. A role may not resolve to a symbol that implies clinical authority,
and the generator is where that could be checked mechanically.

## Using it [#using-it]

**Reference the role, never the glyph, wherever a role exists.** For status,
that means resolving through `CLINICAL_STATUS_META[level].icon` rather than
choosing a lucide component yourself. For the roles with no mapping yet, keep the
glyph in one place per role rather than at each call site, so that the eventual
mapping is a move rather than a hunt.

**Check the accessible-name column before shipping an icon-only control.** If the
role has no default name, the control needs one written for it, describing the
action rather than the picture.

**Do not add a role for a one-off.** A role exists because more than one place
needs the concept. A single screen wanting a specific glyph is a component
detail; a single screen wanting a *new concept* is a design question that
predates the icon.

**Use this page to review coherence.** Icons chosen one at a time drift: two
roles end up with near-identical glyphs, or one group is drawn from a different
visual family. Seeing the whole set in one table is the only reliable way to
notice.

## Tokens [#tokens]

The role mapping is meant to be emitted as machine-readable JSON beside the
catalogue, so an agent generating an interface can resolve a role without
guessing at a glyph name. See
[Machine-readable schemas](../../agents/machine-readable-schemas.mdx). That step
has not been added to `scripts/build-registry.mts`, so the only machine-readable
half today is `CLINICAL_STATUS_META`, which travels with every implemented item.
Sizes and alignment come from the type and space scales, not from here.

<NoDataYet what="The machine-readable role map" script="scripts/build-registry.mts">
  `scripts/build-registry.mts` has no icon step, so there is no emitted role map to
  link to. An agent that needs a status glyph today reads `lib/status.ts`, which
  ships with the component.
</NoDataYet>

## Accessibility impact [#accessibility-impact]

* **The accessible-name column is the point of the table.** Icon-only controls
  without names are the most common accessibility defect involving icons, and the
  fix is to make the name part of the definition rather than something each
  component invents.
* **A closed status set is an accessibility property.** Four levels with four
  glyphs is learnable; nine glyphs across a product is not, and the readers who
  suffer most are those with cognitive disabilities and low digital confidence.
* **Every meaningful glyph is measured for non-text contrast** against the
  surfaces it appears on, under WCAG 2.2 SC 1.4.11, in both themes. The measured
  results appear on
  [Contrast conformance](../../accessibility/contrast-conformance.mdx).
* **Icons are checked under CVD simulation as shapes, not colours.** Two status
  glyphs that differ only by hue are two glyphs that are the same glyph for a
  large group of readers. The audit is on
  [Colour independence](../../accessibility/colour-independence.mdx).

## Related [#related]

* [Health glyphs](./health-glyphs.mdx) says what may never enter this mapping
  and why.
* [Sizing and alignment](./sizing-and-alignment.mdx) shows how these glyphs are
  sized and set beside text.
* [Iconography](./index.mdx) has the four rules that govern the whole set.
