Skip to main content

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.

Version: 2.0.0
Last Updated: 2026-05-13
Scope: All modules. Sizes/colors: UI_UX_STANDARDS.md § Icons, 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 valueCSS classWhen to use
    mutedtext-muted-foregroundDefault for idle nav, secondary chrome
    neutraltext-foregroundBody-level inline icons
    primarytext-primaryActive nav, primary CTAs
    accenttext-accentBrand-accent surfaces
    onAccenttext-accent-foregroundIcons over bg-accent/bg-primary
    successtext-successConfirmations, positive deltas
    warningtext-warningCaution, attention
    destructivetext-destructiveErrors, delete, irreversible
    infotext-infoInfo chips, neutral system messages
    currenttext-currentInherit 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).
// ✅ 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 sizePixelsTailwindUse case
xs14pxh-3.5 w-3.5Dense rows, inline-with-text, table chrome
sm16pxh-4 w-4List items, buttons, form controls, sub-nav
md (default)20pxh-5 w-5Top-level nav items, section headers
lg24pxh-6 w-6Empty 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.
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" />
PropDefaultNotes
sizemd (20px)xs/14, sm/16, md/20, lg/24 — maps 1:1 to the table above.
strokeWidth1.5Platform default (Direction A). Lucide ships 2; we run lighter for the Apple-Minimal Outline feel. Override only via weight.
weightregularthin (1.1) / regular (1.5) / bold (2.0). Ignored when strokeWidth is passed explicitly.
variantoutlinesolid fills the glyph with currentColor and zeroes the stroke. Used for the active-nav treatment.
toneSemantic color shorthand. See the allow-list in §0. Caller-provided className color tokens still win.
decorativetrueSets aria-hidden="true". When false, aria-label is required by TypeScript.
classNameMerged 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

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

DomainConceptIconToken / step keyNotes
UniversalDashboardLayoutDashboardEvery module landing
UniversalSettingsSettingsConfig / preferences
UniversalCreate / AddPlusPrimary create CTA
UniversalPeople listUsersDirectory views
UniversalTeams / groupsUsersRoundGroup views
UniversalForms / docsFileTextTemplates, generated docs
UniversalReportsBarChart3Analytics
UniversalScheduleCalendarDaysScheduling (NOT date pickers)
UniversalTimeClockTime tracking
Status & AlertsApproved / CompleteCheckCircle2completePair with text-success
Status & AlertsWarningAlertTriangleNon-blocking caution
Status & AlertsError / blockingCircleAlertReplaces deprecated AlertCircle
Status & AlertsInfoInfoTooltips, callouts
Status & AlertsDisciplinary (HR PIP)AlertOctagonNOT TrendingDown
Status & AlertsEmergency / incidentSirenNOT AlertOctagon
Compliance & SecurityCompliance / verifiedShieldCheckNOT generic Shield
Compliance & SecurityRisk (GR register)ShieldAlertNOT AlertTriangle
Compliance & SecurityGeneric protectionShieldReserve for non-compliance security
Compliance & SecurityAudit logScrollTextAudit trails
HealthcareMedicationPillE-prescribing, MAT, med list
HealthcareVitalsHeartPulseNOT Heart / Activity alone
HealthcareClinical / providerStethoscopeClinical workflows
HealthcareInjection / immunizationSyringeVaccines, injectables
HealthcarePatient chartClipboardListChart views
Healthcare copy-paste templates: see 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 useUse insteadWhy
CheckCircleCheckCircle2Lucide deprecated outline variant
AlertCircleAlertTriangle or CircleAlertAlertCircle removed; pick by semantic
Home (for module landings)LayoutDashboardModule landings use Dashboard, not Home
Shield (for compliance)ShieldCheckShield reserved for generic protection
TrendingDown (for PIPs)AlertOctagonPerformance vs. disciplinary distinction
Calendar (for scheduling)CalendarDaysCalendar/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.
MappingWhy it’s lockedExample of breakage if changed
Dashboard → LayoutDashboardUsed by every module landing page; nav muscle memorySwapping to Home breaks visual parity across HR/CL/PM/RH/FA
Settings → Settings (gear)Universal config affordanceA wrench/sliders icon would suggest “tools” not “preferences”
Approved/Complete → CheckCircle2Paired with text-success everywhereCheckCircle (deprecated) renders thinner — visual drift in approval flows
Risk → ShieldAlert (NOT AlertTriangle)GR risk register is shield-coded; warnings are triangle-codedConflates “risk” with “warning” in audit reports
Incidents → Siren (NOT AlertOctagon)Disciplinary (HR) uses AlertOctagon; emergencies use SirenHR PIPs and clinical incidents would visually collide
Compliance → ShieldCheck (NOT Shield)Shield = generic security; ShieldCheck = verified/compliantCredentialing badges lose the “verified” cue
Medication → Pill (NOT Capsule)E-prescribing, MAT, med list all standardized on PillMixed iconography in clinical med views
Vitals → HeartPulse (NOT Heart or Activity alone)Heart = favorite/like; Activity = generic monitoringVitals 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 contractTemplate-driven wizards render fallback icon

Quick example

// ✅ 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.
PropValuesEffect
tonesuccess warning destructive info muted neutral primary accent onAccent current inheritApplies 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.
variantoutline (default) solidsolid sets fill="currentColor" and zeroes the stroke for filled glyphs (e.g., CheckCircle2 in success badges).
weightthin (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.
// 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).
PatternWhen to use solidRecommended icons
Status badge — terminal/severeFinal 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 pip14–16 px dot beside a row label where an outline would look like a circle, not a status.CircleDot, CircleAlert, Circle
Hero icon-in-circleErrorState, empty-state cards, onboarding callouts where the icon sits in a tinted bg-{tone}/10 circle.CircleAlert, Info, CircleHelp, ShieldAlert
Avatar / chip overlaySmall status overlay on an avatar (verified, locked, on-call).BadgeCheck, ShieldCheck, Lock
Toast / notification leading iconSonner 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.
// 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:
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 AppIconTones via DOMAIN_STATUS_TONEno new status tokens, and no status-specific tone expansion. Use domainStatusToIconTone() so the same DB enum string drives the icon tone consistently across modules.
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" />
LifecycleStatus keys → tone
Approval / reviewapprovedsuccess · denied, rejecteddestructive · pendingwarning · in_review, submittedinfo
Workflow / taskactive, completedsuccess · in_progress, queued, scheduledinfo · on_hold, pausedwarning · expired, faileddestructive · archived, cancelled, draftmuted
Scheduling / attendance (CE / PM)attended, confirmedsuccess · no_showdestructive
Billing / claims (PM / FA)paidsuccess · partialwarning · overduedestructive · voidmuted
Clinical / patient (CL)dischargedneutral · inactivemuted · prospectinfo
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)

IconMeaningUsage
LayoutDashboardOverview/DashboardModule landing, dashboard views
SettingsSettings/ConfigurationModule settings, preferences
FileTextForms/DocumentsForms, documents, templates
BarChart3Reports/AnalyticsReports, analytics
PlusCreate/AddCreate buttons, add actions
UsersPeople ListEmployee lists, user directories
UsersRoundTeams/GroupsTeam pages, group views
CalendarDate/ScheduleCalendars, scheduling, dates
ClockTimeTime tracking
CheckCircle2Complete/ApprovedApprovals, completed items
Building2Organization/DepartmentDepartments, vendors, organizations
Avoid: CheckCircle (deprecated) — use CheckCircle2. Scheduling: Use CalendarDays for scheduling; Calendar/CalendarIcon for date pickers.

Alert & status (do not interchange)

IconMeaningUse For
AlertTriangleGeneral warningWarnings, caution
CircleAlertIssues/ProblemsIssues list, workflow alerts, findings
ShieldAlertRisk/Security riskRisk registers, security risks
SirenIncidents/EmergencyIncidents, emergencies
AlertOctagonSerious/DisciplinaryPIPs, disciplinary, critical alerts
FlagEvents/FlagsSignificant events, flagged items
BugVulnerabilitiesSecurity vulnerabilities, bugs

Compliance & security

IconMeaningUse For
ShieldSecurity/ProtectionSecurity features
ShieldCheckCompliance/VerifiedCompliance, credentialing
ClipboardCheckAudit/ChecklistAudits, checklists
AwardCertificationCEUs, certificates, accreditations

3. Size, color, and usage patterns

Authority: Scale in src/index.css (--icon-xs--icon-lg); UI_UX_STANDARDS.md § Icons.
// 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 § Accessibility for focus and ARIA.
Example:
<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:
ContextIcon typeHow to pass
WizardShell (presentational)Lucide componentstep.icon = User
PF-41 template JSONString keystep.icon = "user" → resolve via resolveStepIcon()
Common step icon assignments:
Step purposeIcon keyLucide component
Personal infouserUser
Contact detailsmailMail
Dates/schedulingcalendarCalendar
Employment/rolebriefcaseBriefcase
Finance/paymentwalletWallet
Documentsfile-textFileText
Credentialsshield-checkShieldCheck
ConfigurationsettingsSettings
Review/summaryreviewListChecks
CompletioncompleteCheckCircle2
Clinical assessmentstethoscopeStethoscope
Medication / RxpillPill
Injection / lab drawsyringeSyringe
Vitals / biometricsheart-pulseHeartPulse
Behavioral healthbrainBrain
Lab / diagnosticsmicroscopeMicroscope
Treatment / wound carebandageBandage
Symptom / temperaturethermometerThermometer
Health monitoringactivityActivity
Clinical checklistclipboard-listClipboardList
Sizing: size-4 (16px) inside step circles. Step circles are size-9 (desktop) / size-11 (mobile). See: WIZARD_DEVELOPMENT_GUIDE.md § Step Icons and WIZARD_UX_STANDARD.md § Step icons.

6. Module-specific patterns (summary)

ModuleConceptIcon
HROnboarding / OffboardingUserPlus / UserMinus
HRCredentialing / PIPsShieldCheck / AlertOctagon
RHResidences / Significant EventsBuilding2 / Flag
LOGoals / Issues / ScorecardsTarget / CircleAlert / BarChart3
GRPolicy / Risk / AuditFileText / ShieldAlert / CircleAlert
ITAssets / Tickets / VulnerabilitiesLaptop / Ticket / Bug

Healthcare & clinical (CL / PM)

ConceptIconUse For
Clinical exam / vitalsStethoscopeCL intake, vitals capture, clinical assessment
Medication / RxPillE-prescribing, MAT, medication list
Injection / lab drawSyringeVaccinations, injectable medications, lab orders
Vitals / biometricsHeartPulseVitals dashboard, biometrics
Behavioral healthBrainMental health assessments, COD documentation
Lab / diagnosticsMicroscopeLab results, diagnostics
Treatment / wound careBandageTreatment plans, wound documentation
Symptoms / temperatureThermometerSymptom screening, fever check
Health monitoringActivityHealth metrics, monitoring dashboards
Clinical checklistClipboardListIntake 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: CheckCircleCheckCircle2; AlertCircleAlertTriangle or CircleAlert; HomeLayoutDashboard.

8. Adding new icons

  1. Search Lucide icons.
  2. Check if a similar concept exists in this guide.
  3. Prefer semantic icons; document the new icon here before use.

References

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-700tone="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.