Foundations

Icon

Single-stroke icons from Lucide, wired to the size + color tokens. Reach for the semantic registry — <Icon semantic="status.success" /> — when an icon represents a known design intent. That's how the system stays one-icon-per-concept across every surface.

01Why Lucide

One stroke style, one weight, one set of design rules. Trade-offs versus the alternatives are below — we chose against Phosphor's 6-weight variety (consistency risk) and Heroicons' 300-icon cap (you'd hit gaps fast given the status / data / system breadth of the product). Lucide is what shadcn ships and what Vercel / Linear / Cal.com run in production.

SystemWhyTrade-off

Lucide ✓

1,500+ icons · single-stroke · MIT · shadcn-native · tree-shakeable

Single weight only — but that's the point.

Phosphor

7,000+ icons · 6 weights (thin/light/regular/bold/fill/duotone)

Weight-mixing risk · bigger bundle.

Tabler

4,500+ icons · outline + filled

Smaller community · less shadcn momentum.

Heroicons

Well-curated · Tailwind-aligned · outline + solid

~300 icons — you'd hit gaps quickly.

Iconify

200,000+ aggregator across many sets

Multiple stroke styles = consistency chaos.

02Size scale

Five sizes mapped to Tailwind utilities. xs/sm fit inline with text; md is the default UI step; lg/xl are for emphasis and hero illustration.

SampleTokenPxTailwindUse

xs

12px

size-3

Micro / dot indicators

sm

14px

size-3.5

Inline with text-sm

md

16px

size-4

Default UI scale

lg

20px

size-5

Emphasis / standalone

xl

24px

size-6

Hero / illustrative

03Tone scale

Tones reach into the color tokens. inherit is the default — the icon picks up surrounding text color. The status tones (success/warning/error/info) match the alert family. Semantic registry entries carry a suggestedTone so the icon picks the right color automatically.

SampleToneWhen

inherit

Adopts surrounding text color (default — recommended for inline icons)

main

icon-main — strongest icon color, pairs with text-main

body

icon-body — softer, pairs with text-body

body-light

icon-body-light — tertiary, pairs with text-body-light

disabled

icon-disabled — disabled state, pairs with text-disabled

muted

UI meta companion (sidebar labels, captions)

white

icon-white — for icons on solid colored fills (primary button, solid success badge, destructive button)

brand

Brand blue — for hero / accent icons on neutral bg

success

Positive status (vivid green on neutral bg)

warning

Caution (amber on neutral bg)

error

Failure / destructive (red on neutral bg)

info

Neutral information (blue on neutral bg)

primary-ink

Icon on bg-primary-subtle (dark blue ink)

success-ink

Icon on bg-success-subtle (dark green ink)

warning-ink

Icon on bg-warning-subtle (dark amber ink)

error-ink

Icon on bg-error-subtle (dark red ink)

info-ink

Icon on bg-info-subtle (dark blue ink)

04Semantic registry

The "blessed" set — one lucide icon per design intent, plus a suggested tone and a usage note. Reaching for <Icon semantic="status.success" /> guarantees that "success" reads as Check everywhere. Search by semantic key, label, lucide name, or use case. Click any card to copy the snippet.

234 icons

05Patterns

Three usage shapes covering the bulk of real-world code.

Inline with text

Recruit pipeline synced.

Use sm or md, default tone (inherit). The icon picks up the surrounding text color so it stays in family.

<p className="flex items-center gap-1.5">
  <Icon semantic="status.success" size="sm" />
  Recruit pipeline synced.
</p>

Icon-only button

Wrap with an accessible name. The Icon itself can carry aria-label, but pairing it with the button's aria-label is the resilient pattern.

<button aria-label="Delete recruit">
  <Icon semantic="action.delete" size="sm" />
</button>

Trailing CTA arrow

A small ArrowRight that translates on hover signals forward motion. The semantic key navigation.forward keeps it consistent across every CTA in the app.

<button>
  View the rubric
  <Icon semantic="navigation.forward" size="sm"
        className="group-hover:translate-x-0.5
                   transition-transform" />
</button>

Icon on solid colored fill

tone="white" maps to --icon-white. Use it for icons inside primary/destructive buttons, solid status badges — anywhere the surface is a solid colored fill. Inheritance from the parent text color also works, but explicit tone is preferred for readability.

<button className="bg-primary
                     text-white">
  <Icon semantic="action.send" size="sm"
        tone="white" />
  Send offer
</button>

<button className="bg-error
                     text-white">
  <Icon semantic="action.delete" size="sm"
        tone="white" />
  Delete recruit
</button>

Drawn glyphs — the lucide-gap rule

Standard listSmart list

When lucide lacks a mark, ASSEMBLE it from lucide's own paths — ONE svg in lucide grammar (24 viewBox, stroke 2, round caps), exported from its home component so every surface wears the identical mark. ListTypeGlyph is the one today: lucide ships no list-sparkles, so the ListCard sidebar and the ActivityFeed's list tiles share this drawing — plain = standard list, corner sparkle rising to the second line = smart list. Never a second drawing per surface; swap in the real lucide icon if it ever ships.

import { ListTypeGlyph }
  from "@/components/upnext";

<ListTypeGlyph smart={false} />
// standard — the plain list
<ListTypeGlyph smart />
// smart — the corner sparkle

Icon on subtle colored bg

SignedPending visitOffer pulled

The "*-ink" tones map to the matching --*-ink token. Use them when an icon sits on bg-success-subtle, bg-warning-subtle, etc. — alert chips, soft badges, status pills.

<span className="bg-success-subtle
                   text-success-ink">
  <Icon semantic="status.success"
        size="xs" tone="success-ink" />
  Signed
</span>

06Full Lucide library

The escape hatch. Every Lucide icon, searchable. Use this when the registry doesn't carry what you need — and consider adding the icon to lib/icons.ts after you reach for it once or twice. Click any tile to copy the snippet.

3,912 of 3,912 icons

Page 1 of 41