> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Icon Guide

> Version: 2.0.0 Last Updated: 2026-05-13 Scope: All modules. Sizes/colors: UI_UX_STANDARDS.md § Icons, SEMANTIC_COLORS.md.

**Version:** 2.0.0\
**Last Updated:** 2026-05-13\
**Scope:** All modules. Sizes/colors: [UI\_UX\_STANDARDS.md](UI_UX_STANDARDS.md) § Icons, [SEMANTIC\_COLORS.md](SEMANTIC_COLORS.md).

Single reference for icon assignment, sizing, imports, and accessibility.

***

## 0. Direction A — Final Rules (canonical)

These rules are non-negotiable for all new and refactored UI. PR review and the
`npm run audit:icons` script enforce them.

1. **Library:** Lucide-react only. Render through `<AppIcon>` from
   `@/shared/ui/icon`. Raw `<LucideIcon … />` JSX is allowed only inside
   leaf primitives we don't own (e.g. shadcn shells); everywhere else use
   `AppIcon`.
2. **Stroke width:** `1.5` (Apple-Minimal Outline). Never set
   `strokeWidth` inline on a Lucide tag — use `<AppIcon>` so the platform
   default applies, or pass `weight="thin" | "regular" | "bold"` for the
   rare exceptions (`1.1` / `1.5` / `2.0`).
3. **Size scale (locked, 4 tiers):** `xs`/14, `sm`/16, `md`/20, `lg`/24.
   No other sizes. `2xs`/12 exists only for legacy dense table chrome and
   must not be used in new code.
4. **Active-state behavior (navigation):** the active item swaps the
   outline glyph for a **filled** variant — `fill="currentColor"` and
   `strokeWidth={0}` — paired with the `text-primary` tone. Use the
   `getActiveIconSvgProps(isActive)` helper in
   `src/platform/navigation/utils/active-state.ts`. Idle items stay
   outline at `text-muted-foreground`.
5. **Color tokens (allow-list):** the only colors permitted on an icon
   are the semantic tones below — applied via the `tone` prop, never
   hex/`text-gray-*`/`text-blue-*` literals.

   | `tone` value  | CSS class                | When to use                            |
   | ------------- | ------------------------ | -------------------------------------- |
   | `muted`       | `text-muted-foreground`  | Default for idle nav, secondary chrome |
   | `neutral`     | `text-foreground`        | Body-level inline icons                |
   | `primary`     | `text-primary`           | Active nav, primary CTAs               |
   | `accent`      | `text-accent`            | Brand-accent surfaces                  |
   | `onAccent`    | `text-accent-foreground` | Icons over `bg-accent`/`bg-primary`    |
   | `success`     | `text-success`           | Confirmations, positive deltas         |
   | `warning`     | `text-warning`           | Caution, attention                     |
   | `destructive` | `text-destructive`       | Errors, delete, irreversible           |
   | `info`        | `text-info`              | Info chips, neutral system messages    |
   | `current`     | `text-current`           | Inherit current text color             |
   | `inherit`     | (none)                   | Caller controls color via parent       |

   Anything outside this list — including raw palette classes
   (`text-blue-500`, `#0EA5E9`) and per-module hex values — is rejected by
   the icon audit and PR review.
6. **Variants:** `variant="outline"` (default) for the line look, or
   `variant="solid"` to fill the glyph with `currentColor` (zeroes the
   stroke). The active-state filled treatment in rule 4 is the canonical
   use of `solid`.
7. **Accessibility:** decorative icons (default) get `aria-hidden="true"`
   automatically; meaningful icons must pass `decorative={false}` and a
   non-empty `aria-label` (TypeScript enforces this).

```tsx theme={null}
// ✅ canonical idle nav item
<AppIcon icon={Inbox} size="sm" tone="muted" />

// ✅ canonical active nav item (filled, primary tone)
<AppIcon icon={Inbox} size="sm" tone="primary" variant="solid" />

// ✅ icon-only button
<AppIcon icon={Trash2} size="sm" tone="destructive" decorative={false} aria-label="Delete row" />

// ❌ raw Lucide with palette color and inline stroke
<Inbox className="h-4 w-4 text-blue-500" strokeWidth={1.75} />
```

***

## 1. Quick Reference

### Size tokens (Tailwind)

The scale is locked to four tiers. Do not introduce new sizes.

| `AppIcon size` | Pixels | Tailwind      | Use case                                    |
| -------------- | ------ | ------------- | ------------------------------------------- |
| `xs`           | 14px   | `h-3.5 w-3.5` | Dense rows, inline-with-text, table chrome  |
| `sm`           | 16px   | `h-4 w-4`     | List items, buttons, form controls, sub-nav |
| `md` (default) | 20px   | `h-5 w-5`     | Top-level nav items, section headers        |
| `lg`           | 24px   | `h-6 w-6`     | Empty states, feature/illustrative icons    |

Anything larger (32 / 48 px) is brand or illustration, **not** an icon —
render the brand SVG (`public/brand/*`) or a dedicated illustration
component, not a Lucide glyph.

### `AppIcon` wrapper (preferred for shared/component code)

`AppIcon` from `@/shared/ui/icon` is the **required** way to render a Lucide icon in shared, platform, and core code. It enforces the locked stroke width, size scale, and color allow-list, and standardizes the decorative-vs-meaningful accessibility contract.

```tsx theme={null}
import { Inbox, Trash2 } from 'lucide-react';
import { AppIcon } from '@/shared/ui/icon';

// Decorative (defaults to aria-hidden="true")
<AppIcon icon={Inbox} size="lg" tone="muted" />

// Meaningful (TypeScript requires aria-label)
<AppIcon icon={Trash2} size="sm" decorative={false} aria-label="Delete row" />
```

| Prop          | Default     | Notes                                                                                                                            |
| ------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `size`        | `md` (20px) | `xs`/14, `sm`/16, `md`/20, `lg`/24 — maps 1:1 to the table above.                                                                |
| `strokeWidth` | `1.5`       | Platform default (Direction A). Lucide ships `2`; we run lighter for the Apple-Minimal Outline feel. Override only via `weight`. |
| `weight`      | `regular`   | `thin` (1.1) / `regular` (1.5) / `bold` (2.0). Ignored when `strokeWidth` is passed explicitly.                                  |
| `variant`     | `outline`   | `solid` fills the glyph with `currentColor` and zeroes the stroke. Used for the active-nav treatment.                            |
| `tone`        | —           | Semantic color shorthand. See the allow-list in §0. Caller-provided `className` color tokens still win.                          |
| `decorative`  | `true`      | Sets `aria-hidden="true"`. When `false`, `aria-label` is **required** by TypeScript.                                             |
| `className`   | —           | Merged via `cn`. Prefer `tone` for color; reserve `className` for layout (`shrink-0`, `mr-2`, `transition-transform`).           |

Raw `<LucideIcon className="h-4 w-4" />` is no longer acceptable in new code. Existing leaf-primitive usages are tracked in `reports/icon-audit-latest.json` and must be migrated when the surrounding file is touched.

### Import pattern

```typescript theme={null}
import { LayoutDashboard, Users, Building2 } from "lucide-react";
```

### Accessibility

* **Icon-only buttons:** `aria-label="Action name"`.
* **Decorative (with visible text):** `aria-hidden="true"` on the icon.

***

## 1.5 Approved Semantic Icons — Quick Reference

A scannable cheat sheet curated from §2 (universal/status/compliance), §5 (wizard step keys), §6 (module + healthcare), and §7 (anti-patterns). For full detail, jump to those sections.

> **Audit:** Run `npm run audit:icons` to find raw Lucide / inline-stroke / hardcoded-color offenders in `src/shared/components/**` and `src/platform/**`. Latest snapshot JSON: `reports/icon-audit-latest.json`.
>
> **Approval audit:** Run `npm run audit:icon-approvals` (or `…:strict` to fail CI) to flag deprecated Lucide imports (e.g. `CheckCircle`, `AlertCircle`) and DO NOT CHANGE mapping violations (context phrase + wrong icon in same JSX). Report: `reports/icon-approvals-latest.json`.

### Approved icons by concept

| Domain                | Concept                  | Icon              | Token / step key | Notes                               |
| --------------------- | ------------------------ | ----------------- | ---------------- | ----------------------------------- |
| Universal             | Dashboard                | `LayoutDashboard` | —                | Every module landing                |
| Universal             | Settings                 | `Settings`        | —                | Config / preferences                |
| Universal             | Create / Add             | `Plus`            | —                | Primary create CTA                  |
| Universal             | People list              | `Users`           | —                | Directory views                     |
| Universal             | Teams / groups           | `UsersRound`      | —                | Group views                         |
| Universal             | Forms / docs             | `FileText`        | —                | Templates, generated docs           |
| Universal             | Reports                  | `BarChart3`       | —                | Analytics                           |
| Universal             | Schedule                 | `CalendarDays`    | —                | Scheduling (NOT date pickers)       |
| Universal             | Time                     | `Clock`           | —                | Time tracking                       |
| Status & Alerts       | Approved / Complete      | `CheckCircle2`    | `complete`       | Pair with `text-success`            |
| Status & Alerts       | Warning                  | `AlertTriangle`   | —                | Non-blocking caution                |
| Status & Alerts       | Error / blocking         | `CircleAlert`     | —                | Replaces deprecated `AlertCircle`   |
| Status & Alerts       | Info                     | `Info`            | —                | Tooltips, callouts                  |
| Status & Alerts       | Disciplinary (HR PIP)    | `AlertOctagon`    | —                | NOT `TrendingDown`                  |
| Status & Alerts       | Emergency / incident     | `Siren`           | —                | NOT `AlertOctagon`                  |
| Compliance & Security | Compliance / verified    | `ShieldCheck`     | —                | NOT generic `Shield`                |
| Compliance & Security | Risk (GR register)       | `ShieldAlert`     | —                | NOT `AlertTriangle`                 |
| Compliance & Security | Generic protection       | `Shield`          | —                | Reserve for non-compliance security |
| Compliance & Security | Audit log                | `ScrollText`      | —                | Audit trails                        |
| Healthcare            | Medication               | `Pill`            | —                | E-prescribing, MAT, med list        |
| Healthcare            | Vitals                   | `HeartPulse`      | —                | NOT `Heart` / `Activity` alone      |
| Healthcare            | Clinical / provider      | `Stethoscope`     | —                | Clinical workflows                  |
| Healthcare            | Injection / immunization | `Syringe`         | —                | Vaccines, injectables               |
| Healthcare            | Patient chart            | `ClipboardList`   | —                | Chart views                         |

> **Healthcare copy-paste templates:** see [`examples/wizard-templates/healthcare-icon-keys.json`](./examples/wizard-templates/healthcare-icon-keys.json) for ready-to-use PF-41 snippets using `stethoscope`, `pill`, and `syringe`.

\| Wizard steps | Personal info | `User` | `user` | PF-41 template key |
\| Wizard steps | Contact info | `Mail` | `mail` | PF-41 template key |
\| Wizard steps | Schedule | `CalendarDays` | `calendar` | PF-41 template key |
\| Wizard steps | Review & submit | `ClipboardCheck` | `review` | PF-41 template key |
\| Wizard steps | Complete | `CheckCircle2` | `complete` | PF-41 template key |
\| Wizard steps | Unknown key | `CircleHelp` | *(fallback)* | Auto-rendered + dev console warning by `resolveStepIcon()` |
\| Module-specific | HR | `Users` | — | See §6 |
\| Module-specific | CL | `Stethoscope` | — | See §6 |
\| Module-specific | PM | `ClipboardList` | — | See §6 |
\| Module-specific | FA | `DollarSign` | — | See §6 |
\| Module-specific | RH | `Home` | — | Recovery housing (residential) |
\| Module-specific | GR | `ShieldCheck` | — | Governance & risk |
\| Module-specific | CE | `Megaphone` | — | Community engagement |

### Preferred vs. deprecated variants

| Don't use                                                     | Use instead                        | Why                                                 |
| ------------------------------------------------------------- | ---------------------------------- | --------------------------------------------------- |
| `CheckCircle`                                                 | `CheckCircle2`                     | Lucide deprecated outline variant                   |
| `AlertCircle`                                                 | `AlertTriangle` or `CircleAlert`   | `AlertCircle` removed; pick by semantic             |
| `Home` (for module landings)                                  | `LayoutDashboard`                  | Module landings use Dashboard, not Home             |
| `Shield` (for compliance)                                     | `ShieldCheck`                      | `Shield` reserved for generic protection            |
| `TrendingDown` (for PIPs)                                     | `AlertOctagon`                     | Performance vs. disciplinary distinction            |
| `Calendar` (for scheduling)                                   | `CalendarDays`                     | `Calendar`/`CalendarIcon` reserved for date pickers |
| Raw `<LucideIcon className="h-4 w-4" />` in shared components | `<AppIcon icon={...} size="sm" />` | Enforces stroke width + a11y contract               |

### DO NOT CHANGE mappings

These mappings are locked across cores. Changing them creates cross-module visual drift or breaks template-driven wizards.

| Mapping                                                  | Why it's locked                                                 | Example of breakage if changed                                              |
| -------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Dashboard → `LayoutDashboard`                            | Used by every module landing page; nav muscle memory            | Swapping to `Home` breaks visual parity across HR/CL/PM/RH/FA               |
| Settings → `Settings` (gear)                             | Universal config affordance                                     | A wrench/sliders icon would suggest "tools" not "preferences"               |
| Approved/Complete → `CheckCircle2`                       | Paired with `text-success` everywhere                           | `CheckCircle` (deprecated) renders thinner — visual drift in approval flows |
| Risk → `ShieldAlert` (NOT `AlertTriangle`)               | GR risk register is shield-coded; warnings are triangle-coded   | Conflates "risk" with "warning" in audit reports                            |
| Incidents → `Siren` (NOT `AlertOctagon`)                 | Disciplinary (HR) uses `AlertOctagon`; emergencies use `Siren`  | HR PIPs and clinical incidents would visually collide                       |
| Compliance → `ShieldCheck` (NOT `Shield`)                | `Shield` = generic security; `ShieldCheck` = verified/compliant | Credentialing badges lose the "verified" cue                                |
| Medication → `Pill` (NOT `Capsule`)                      | E-prescribing, MAT, med list all standardized on `Pill`         | Mixed iconography in clinical med views                                     |
| Vitals → `HeartPulse` (NOT `Heart` or `Activity` alone)  | `Heart` = favorite/like; `Activity` = generic monitoring        | Vitals dashboard mistaken for favorites/analytics                           |
| Wizard "Personal info" step → `User` (key `user`)        | PF-41 templates resolve string keys via `resolveStepIcon()`     | Renaming the key breaks every template JSON referencing it                  |
| Wizard "Complete" step → `CheckCircle2` (key `complete`) | Same template JSON contract                                     | Template-driven wizards render fallback icon                                |

### Quick example

```tsx theme={null}
// ✅ Approved
<AppIcon icon={ShieldCheck} size="sm" className="text-success" />
<AppIcon icon={CheckCircle2} size="sm" decorative={false} aria-label="Approved" />

// ❌ Avoid
<CheckCircle className="h-4 w-4 text-green-500" />   // deprecated icon + raw color
<Shield className="h-4 w-4" />                        // generic, not "compliant"
```

### Tone & variant (v1.3.0)

`AppIcon` accepts three additive props that eliminate hand-typed semantic
color classes and stroke overrides. All are optional — omitting them is
byte-identical to the legacy behavior.

| Prop      | Values                                                                                                       | Effect                                                                                                                                                                                                                                                            |
| --------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tone`    | `success` `warning` `destructive` `info` `muted` `neutral` `primary` `accent` `onAccent` `current` `inherit` | Applies the matching `text-*` semantic token. `accent` = `text-accent` (default on subtle accent tints like `bg-accent/10`), `onAccent` = `text-accent-foreground` (for solid `bg-accent` surfaces). `current` = inherit from parent; `inherit` = no class added. |
| `variant` | `outline` (default) `solid`                                                                                  | `solid` sets `fill="currentColor"` and zeroes the stroke for filled glyphs (e.g., `CheckCircle2` in success badges).                                                                                                                                              |
| `weight`  | `thin` (1.25) `regular` (1.75) `bold` (2.25)                                                                 | Stroke shorthand. Explicit `strokeWidth={n}` always wins.                                                                                                                                                                                                         |

Resolution order on the rendered SVG (last wins): size class → tone class →
caller `className`. So one-off escapes like `className="text-amber-700"`
still override a `tone`.

```tsx theme={null}
// Before — hand-typed token, easy to drift
<AppIcon icon={CheckCircle2} size="sm" className="text-success" />

// After — tone shorthand, registry-backed
<AppIcon icon={CheckCircle2} size="sm" tone="success" />

// Filled glyph for solid badges
<AppIcon icon={CircleAlert} size="sm" tone="destructive" variant="solid" />
```

#### Solid variant cookbook

`variant="solid"` flips the glyph from outlined to filled (sets
`fill="currentColor"` and zeroes the stroke). It's a **visual weight**
choice, not a semantic one — tone still carries the meaning. Reach for
`solid` only when the icon needs to read as a single dense anchor:
a tinted badge chip, a status pip in dense tables, an avatar overlay,
or an icon-in-circle hero on an empty/error state. Default to
`outline` everywhere else (toolbars, nav, body copy, form affordances).

| Pattern                               | When to use `solid`                                                                                                                         | Recommended icons                                        |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **Status badge — terminal/severe**    | Final states that need to "pop" in a list (paid, denied, no-show, void, overdue, failed). Pair with `StatusBadge`'s `iconVariant: 'solid'`. | `CheckCircle2`, `XCircle`, `CircleAlert`, `OctagonAlert` |
| **Inline severity pip**               | 14–16 px dot beside a row label where an outline would look like a circle, not a status.                                                    | `CircleDot`, `CircleAlert`, `Circle`                     |
| **Hero icon-in-circle**               | `ErrorState`, empty-state cards, onboarding callouts where the icon sits in a tinted `bg-{tone}/10` circle.                                 | `CircleAlert`, `Info`, `CircleHelp`, `ShieldAlert`       |
| **Avatar / chip overlay**             | Small status overlay on an avatar (verified, locked, on-call).                                                                              | `BadgeCheck`, `ShieldCheck`, `Lock`                      |
| **Toast / notification leading icon** | Sonner toast variants where a filled glyph improves contrast against the toast surface.                                                     | `CheckCircle2`, `CircleAlert`, `Info`                    |

**Don't use `solid` for:**

* Toolbar / icon-button affordances (`Pencil`, `Trash2`, `Search`,
  `Filter`) — outline is the default app feel.
* Navigation rails and sidebar items — outline keeps the chrome quiet.
* Decorative inline icons next to body text — solid will dominate the
  line.
* Anything where the icon is larger than `lg` (24 px) — at hero sizes
  the filled shape can read as a logo.

```tsx theme={null}
// Status badge — solid for terminal/severe states
import { CheckCircle2, XCircle, CircleAlert } from 'lucide-react';
import { createStatusBadge } from '@/shared/components/StatusBadge';

type ClaimStatus = 'paid' | 'pending' | 'denied' | 'overdue';

export const ClaimStatusBadge = createStatusBadge<ClaimStatus>({
  paid:    { label: 'Paid',    variant: 'success',     icon: CheckCircle2, iconTone: 'success',     iconVariant: 'solid' },
  pending: { label: 'Pending', variant: 'warning',     icon: CircleAlert,  iconTone: 'warning'      /* outline */ },
  denied:  { label: 'Denied',  variant: 'destructive', icon: XCircle,      iconTone: 'destructive', iconVariant: 'solid' },
  overdue: { label: 'Overdue', variant: 'destructive', icon: CircleAlert,  iconTone: 'destructive', iconVariant: 'solid' },
});

// Hero icon-in-circle — solid reads as a single anchor inside the tint
<div className="rounded-full bg-destructive/10 p-3">
  <AppIcon icon={CircleAlert} size="lg" tone="destructive" variant="solid" />
</div>

// Sonner toast leading icon
toast.success('Claim submitted', {
  icon: <AppIcon icon={CheckCircle2} size="sm" tone="success" variant="solid" />,
});

// ❌ Don't: solid on a toolbar button — too heavy
<AppIcon icon={Pencil} size="sm" variant="solid" />
// ✅ Do:
<AppIcon icon={Pencil} size="sm" tone="muted" />
```

**Where this is already wired in shared components**

* `ErrorState` — hero icon uses `variant="solid"` inside the tinted
  circle wrapper (`destructive` and `warning` severities).
* `StatusBadge` / `createStatusBadge<T>()` — accepts
  `iconVariant: 'solid' | 'outline'` per status key, so domain badges
  opt in to solid only for the states that warrant the extra weight.

For status-driven UIs, derive the tone from the registry instead of
hard-coding it:

```tsx theme={null}
import { semanticStatusToIconTone } from '@/shared/lib/semantic-colors';
<AppIcon icon={Icon} size="xs" tone={semanticStatusToIconTone(status)} />
```

#### Domain status aliases (v1.4.0)

Many UIs render statuses that aren't one of the six core
`SemanticStatus` values — workflow lifecycle (`draft`, `in_progress`,
`completed`), approvals (`approved`, `pending`, `rejected`), scheduling
(`confirmed`, `cancelled`, `no_show`), billing (`paid`, `overdue`,
`void`), and clinical (`discharged`, `prospect`). These are all
aliased to existing `AppIconTone`s via `DOMAIN_STATUS_TONE` — **no new
status tokens, and no status-specific tone expansion**. Use
`domainStatusToIconTone()` so the same DB enum string drives the icon
tone consistently across modules.

```tsx theme={null}
import { domainStatusToIconTone } from '@/shared/lib/semantic-colors';

// CE lead, PM claim, FW workflow run — all use the same helper.
<AppIcon icon={CheckCircle2} size="sm" tone={domainStatusToIconTone(lead.status)} />
<AppIcon icon={CircleAlert} size="sm" tone={domainStatusToIconTone(claim.status)} variant="solid" />
```

| Lifecycle                             | Status keys → tone                                                                                                                                                                                     |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Approval / review**                 | `approved` → `success` · `denied`, `rejected` → `destructive` · `pending` → `warning` · `in_review`, `submitted` → `info`                                                                              |
| **Workflow / task**                   | `active`, `completed` → `success` · `in_progress`, `queued`, `scheduled` → `info` · `on_hold`, `paused` → `warning` · `expired`, `failed` → `destructive` · `archived`, `cancelled`, `draft` → `muted` |
| **Scheduling / attendance** (CE / PM) | `attended`, `confirmed` → `success` · `no_show` → `destructive`                                                                                                                                        |
| **Billing / claims** (PM / FA)        | `paid` → `success` · `partial` → `warning` · `overdue` → `destructive` · `void` → `muted`                                                                                                              |
| **Clinical / patient** (CL)           | `discharged` → `neutral` · `inactive` → `muted` · `prospect` → `info`                                                                                                                                  |

Notes:

* Input is normalized (`PascalCase`, `kebab-case`, `Title Case` all
  work), so DB enums can be passed verbatim.
* Unknown keys fall back to `tone="muted"` — the UI never breaks on a
  new/unmapped status, mirroring the wizard-step icon fallback (§5).
* To add a new status, edit `DOMAIN_STATUS_TONE` and this table only;
  do **not** introduce a new `AppIconTone` value.

### Cross-references

* Full universal/status/compliance tables → §2 (Assignment Principles).
* Wizard step string keys + `resolveStepIcon()` contract → §5.
* Module + healthcare icon catalog → §6.
* Anti-patterns and deprecations → §7.

***

## 2. Assignment Principles

* **Semantic consistency:** Same concept → same icon across modules.
* **Visual differentiation:** Similar but distinct concepts → different icons.
* **Accessibility:** Sufficient contrast; pair with text labels.

### Universal icons (DO NOT CHANGE)

| Icon              | Meaning                 | Usage                               |
| ----------------- | ----------------------- | ----------------------------------- |
| `LayoutDashboard` | Overview/Dashboard      | Module landing, dashboard views     |
| `Settings`        | Settings/Configuration  | Module settings, preferences        |
| `FileText`        | Forms/Documents         | Forms, documents, templates         |
| `BarChart3`       | Reports/Analytics       | Reports, analytics                  |
| `Plus`            | Create/Add              | Create buttons, add actions         |
| `Users`           | People List             | Employee lists, user directories    |
| `UsersRound`      | Teams/Groups            | Team pages, group views             |
| `Calendar`        | Date/Schedule           | Calendars, scheduling, dates        |
| `Clock`           | Time                    | Time tracking                       |
| `CheckCircle2`    | Complete/Approved       | Approvals, completed items          |
| `Building2`       | Organization/Department | Departments, vendors, organizations |

**Avoid:** `CheckCircle` (deprecated) — use `CheckCircle2`. **Scheduling:** Use `CalendarDays` for scheduling; `Calendar`/`CalendarIcon` for date pickers.

### Alert & status (do not interchange)

| Icon            | Meaning              | Use For                                |
| --------------- | -------------------- | -------------------------------------- |
| `AlertTriangle` | General warning      | Warnings, caution                      |
| `CircleAlert`   | Issues/Problems      | Issues list, workflow alerts, findings |
| `ShieldAlert`   | Risk/Security risk   | Risk registers, security risks         |
| `Siren`         | Incidents/Emergency  | Incidents, emergencies                 |
| `AlertOctagon`  | Serious/Disciplinary | PIPs, disciplinary, critical alerts    |
| `Flag`          | Events/Flags         | Significant events, flagged items      |
| `Bug`           | Vulnerabilities      | Security vulnerabilities, bugs         |

### Compliance & security

| Icon             | Meaning             | Use For                            |
| ---------------- | ------------------- | ---------------------------------- |
| `Shield`         | Security/Protection | Security features                  |
| `ShieldCheck`    | Compliance/Verified | Compliance, credentialing          |
| `ClipboardCheck` | Audit/Checklist     | Audits, checklists                 |
| `Award`          | Certification       | CEUs, certificates, accreditations |

***

## 3. Size, color, and usage patterns

**Authority:** Scale in `src/index.css` (`--icon-xs` … `--icon-lg`); [UI\_UX\_STANDARDS.md](UI_UX_STANDARDS.md) § Icons.

```tsx theme={null}
// Standard
<Button>
  <Plus className="h-5 w-5 mr-2" />
  Add New
</Button>

// Semantic color
<ShieldCheck className="h-5 w-5 text-success" />
<UserMinus className="h-5 w-5 text-destructive" />
```

**Color:** Use semantic tokens (`text-success`, `text-warning`, `text-destructive`, `text-muted-foreground`). Do not use `text-blue-500`, `text-green-600`, etc.

***

## 4. Accessibility

* **Icon-only buttons:** Always `aria-label` (e.g. `aria-label="View dashboard"`).
* **Decorative icon next to text:** `aria-hidden="true"` on the icon.
* See [UI\_UX\_STANDARDS.md](UI_UX_STANDARDS.md) § Accessibility for focus and ARIA.

Example:

```tsx theme={null}
<button aria-label="View dashboard">
  <LayoutDashboard className="h-5 w-5" />
</button>

<div>
  <Clock className="h-5 w-5" aria-hidden="true" />
  <span>Time Clock</span>
</div>
```

***

## 5. Wizard step icons

Wizard steps use icons inside step circle indicators for visual orientation. The platform provides a curated catalog at `src/platform/wizards/utils/stepIcons.ts`.

**Two usage modes:**

| Context                        | Icon type        | How to pass                                            |
| ------------------------------ | ---------------- | ------------------------------------------------------ |
| `WizardShell` (presentational) | Lucide component | `step.icon = User`                                     |
| PF-41 template JSON            | String key       | `step.icon = "user"` → resolve via `resolveStepIcon()` |

**Common step icon assignments:**

| Step purpose           | Icon key         | Lucide component |
| ---------------------- | ---------------- | ---------------- |
| Personal info          | `user`           | `User`           |
| Contact details        | `mail`           | `Mail`           |
| Dates/scheduling       | `calendar`       | `Calendar`       |
| Employment/role        | `briefcase`      | `Briefcase`      |
| Finance/payment        | `wallet`         | `Wallet`         |
| Documents              | `file-text`      | `FileText`       |
| Credentials            | `shield-check`   | `ShieldCheck`    |
| Configuration          | `settings`       | `Settings`       |
| Review/summary         | `review`         | `ListChecks`     |
| Completion             | `complete`       | `CheckCircle2`   |
| Clinical assessment    | `stethoscope`    | `Stethoscope`    |
| Medication / Rx        | `pill`           | `Pill`           |
| Injection / lab draw   | `syringe`        | `Syringe`        |
| Vitals / biometrics    | `heart-pulse`    | `HeartPulse`     |
| Behavioral health      | `brain`          | `Brain`          |
| Lab / diagnostics      | `microscope`     | `Microscope`     |
| Treatment / wound care | `bandage`        | `Bandage`        |
| Symptom / temperature  | `thermometer`    | `Thermometer`    |
| Health monitoring      | `activity`       | `Activity`       |
| Clinical checklist     | `clipboard-list` | `ClipboardList`  |

**Sizing:** `size-4` (16px) inside step circles. Step circles are `size-9` (desktop) / `size-11` (mobile).

**See:** [WIZARD\_DEVELOPMENT\_GUIDE.md](WIZARD_DEVELOPMENT_GUIDE.md) § Step Icons and [WIZARD\_UX\_STANDARD.md](WIZARD_UX_STANDARD.md) § Step icons.

***

## 6. Module-specific patterns (summary)

| Module | Concept                            | Icon                                       |
| ------ | ---------------------------------- | ------------------------------------------ |
| HR     | Onboarding / Offboarding           | `UserPlus` / `UserMinus`                   |
| HR     | Credentialing / PIPs               | `ShieldCheck` / `AlertOctagon`             |
| RH     | Residences / Significant Events    | `Building2` / `Flag`                       |
| LO     | Goals / Issues / Scorecards        | `Target` / `CircleAlert` / `BarChart3`     |
| GR     | Policy / Risk / Audit              | `FileText` / `ShieldAlert` / `CircleAlert` |
| IT     | Assets / Tickets / Vulnerabilities | `Laptop` / `Ticket` / `Bug`                |

### Healthcare & clinical (CL / PM)

| Concept                | Icon            | Use For                                          |
| ---------------------- | --------------- | ------------------------------------------------ |
| Clinical exam / vitals | `Stethoscope`   | CL intake, vitals capture, clinical assessment   |
| Medication / Rx        | `Pill`          | E-prescribing, MAT, medication list              |
| Injection / lab draw   | `Syringe`       | Vaccinations, injectable medications, lab orders |
| Vitals / biometrics    | `HeartPulse`    | Vitals dashboard, biometrics                     |
| Behavioral health      | `Brain`         | Mental health assessments, COD documentation     |
| Lab / diagnostics      | `Microscope`    | Lab results, diagnostics                         |
| Treatment / wound care | `Bandage`       | Treatment plans, wound documentation             |
| Symptoms / temperature | `Thermometer`   | Symptom screening, fever check                   |
| Health monitoring      | `Activity`      | Health metrics, monitoring dashboards            |
| Clinical checklist     | `ClipboardList` | Intake forms, screening checklists               |

***

## 7. Anti-patterns

* Do not use `AlertTriangle` for everything; differentiate by context.
* Do not use `TrendingDown` for negative performance; use `AlertOctagon` for PIPs.
* Do not use `Shield` for compliance; use `ShieldCheck`.
* Do not use icons without labels for interactive elements.
* Do not `import * as Icons from "lucide-react"`; import only what you use.

**Deprecated:** `CheckCircle` → `CheckCircle2`; `AlertCircle` → `AlertTriangle` or `CircleAlert`; `Home` → `LayoutDashboard`.

***

## 8. Adding new icons

1. Search [Lucide icons](https://lucide.dev/icons).
2. Check if a similar concept exists in this guide.
3. Prefer semantic icons; document the new icon here before use.

***

## References

* [UI\_UX\_STANDARDS.md](UI_UX_STANDARDS.md) § Icons
* [SEMANTIC\_COLORS.md](SEMANTIC_COLORS.md)
* [Lucide React](https://lucide.dev/guide/packages/lucide-react)
* [WCAG non-text content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html)

## Lint enforcement

`AppIcon` usages are guarded by `scripts/audit/audit-appicon-tones.ts`, wired into `validate:governance`. Four rules keep `tone` as the single source of truth for icon color:

* **APPICON-001** (failure) — `className` carries a semantic color token (e.g. `text-success`) **and** `tone` is set. Pick one.
* **APPICON-002** (failure) — `className` carries a Tailwind palette class (e.g. `text-amber-700`). Use `tone` or a semantic token; palette classes don't react to dark mode or PF-95 tenant theming.
* **APPICON-003** (failure) — `tone` literal isn't a member of `AppIconTone` (typos like `tone="successful"`).
* **APPICON-004** (warning) — `variant="solid"` paired with `weight=…` (weight is ignored when solid forces stroke-width 0).

Run locally: `npm run audit:appicon-tones` (human table) or `npm run audit:appicon-tones:json` (writes `reports/audit-appicon-tones.json`). Dynamic `tone={…}` / `className={cn(...)}` expressions are skipped with an info log, never failed.

### Autofix

Most APPICON-001/002/004 violations are mechanical. Run `npm run audit:appicon-tones:fix` to rewrite them in place: dropping duplicate semantic className tokens (Fix A), converting standalone `text-success`/`text-muted-foreground`/etc. classes into the matching `tone` shorthand (Fix B), and removing dead `weight` props when `variant="solid"` is set (Fix D). Use `npm run audit:appicon-tones:fix:dry` to preview rewrites in `reports/audit-appicon-tones-fix-preview.json` without touching files.

`npm run audit:appicon-tones:fix:aggressive` additionally maps Tailwind palette classes (`text-amber-700` → `tone="warning"`, etc.) to the closest semantic tone (Fix C). Palette → tone mapping is judgment-laden — **always diff-review before committing**. APPICON-003 typos (e.g. `tone="successful"`) and dynamic `className={cn(...)}` expressions are never autofixed; the script reports them for manual fix.
