Foundations
Icons
Icons in PRIZM use lucide-react — a wide, well-maintained, MIT-licensed set bundled as React components. No remote fetches, air-gap safe out of the box. PRIZM overrides the stroke-width site-wide for a lighter, more precise aesthetic.
Stroke width
Lucide's default stroke-width is 2. PRIZM overrides to 1.5 via a single rule in app/globals.css:
@layer base {
svg {
stroke-width: 1.5;
}
}Default (lucide stock)
stroke-width: 2
PRIZM site-wide
stroke-width: 1.5
The lighter stroke reads as more precise and pairs better with PRIZM's typography weights. The override applies to ALL svg elements site-wide. To deviate on a single icon, use the Tailwind arbitrary modifier ([stroke-width:2]) or inline style. The lucide strokeWidth prop is overridden by the CSS rule, so prop-level changes have no effect.
Size scale
Five sizes cover essentially every use case. Apply via Tailwind class on the icon element. Sizes are not enforced by tokens — the goal is to keep choices small and predictable, not to gate them.
| Class | Pixels | Use case | Sample |
|---|---|---|---|
| h-3 w-3 | 12px | Inside Badge, dense status indicators, kbd glyphs | |
| h-3.5 w-3.5 | 14px | Small buttons (size sm), breadcrumb separators | |
| h-4 w-4 | 16px (default) | Most buttons, inline with body text, command palette | |
| h-5 w-5 | 20px | Section headers, icon rail items, foundation cards | |
| h-6 w-6 | 24px | Empty states, hero placements, oversized affordances |
Pairing with text
Use inline-flex items-center and an explicit gap that scales with the text. Vertical centering is rarely correct by default — let flex align them.
h-4 w-4 + text-sm + gap-2
Default for buttons and inline calls to action
h-3.5 w-3.5 + text-xs + gap-1.5
Captions, hints, status chips
h-5 w-5 + text-base + gap-3
Section headers, nav rail tooltips when expanded
Accessibility
The two rules that cover 95% of cases:
Decorative (alongside a visible label)
Add aria-hidden so screen readers skip the icon. The label conveys the meaning.
<Button>
<Check className="h-4 w-4" aria-hidden />
Save changes
</Button>Meaningful (icon-only control)
Put aria-label on the container, not the icon. The icon stays decorative.
<Button variant="ghost" size="icon" aria-label="Settings">
<Settings className="h-4 w-4" aria-hidden />
</Button>Common icons
The 30 icons PRIZM uses most across components and templates. Not a full catalogue — lucide ships ~1,500 icons; any of them can be imported the same way.
Actions
CheckXPlusCopySearchSettingsArrowRightArrowLeftChevrons
ChevronDownChevronUpChevronRightChevronLeftStatus & feedback
InfoAlertTriangleAlertCircleCheckCircleBellPeople & containers
UserUsersHomeFileTextLayoutDashboardLayoutGridMessageSquareEyeTheme & state
SunMoonDropletMisc
GithubExternalLinkImport any of these by name from lucide-react:
import { Check, Search, Settings } from "lucide-react";Custom SVGs
For brand marks and product-specific glyphs lucide doesn't cover, write the SVG inline. Set stroke-width on each <path> directly — the site-wide svg { stroke-width: 1.5 } only affects elements that don't have an explicit per-path value. Include a <title> for accessibility.
PRIZM brand mark
Used in the site header and C3 App Shell
<svg
viewBox="0 0 24 24"
fill="none"
className="h-5 w-5 text-accent"
aria-hidden
>
<title>PRIZM</title>
<path
d="M12 2L22 20H2L12 2Z"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
<path
d="M12 2L12 20"
stroke="currentColor"
strokeWidth="2"
strokeLinejoin="round"
/>
</svg>Air-gap
lucide-react ships every icon as a React component bundled into your build — no remote fetches. PRIZM's air-gap audit (pnpm audit:airgap) does not flag lucide. Custom SVGs vendored inline are also safe. The only thing that would break air-gap discipline is referencing a remote icon font or external SVG URL — don't do that.