Icons
Two complementary libraries — lucide for UI, simple-icons for brand logos. Per-framework packages.
Two libraries, zero overlap
Lucide v1.0 removed brand logos (trademark, maintenance). Simple Icons ships only brand logos, no UI primitives. The two packages cover disjoint domains — you always know which one to reach for.
Lucide
UI primitives — arrows, form states, navigation, status.
Simple Icons
Brand logos — GitHub, X, Figma, Stripe, and ~3200 more.
UI icons — Lucide
Import individually for tree-shaking. Default size is 1em — override with size-4 or size-5 utilities.
Brand logos — Simple Icons
Prefixed with Si. Single-color logos — colored variants exist with a Hex suffix (SiGithubHex).
Per-framework packages
Both libraries ship framework-specific packages with identical icon names. Blocks and components are copy-paste per framework — imports swap automatically.
Coloring
Icons inherit color via currentColor. The parent element sets the text color — the icon follows. Variants (info, success, warning, destructive) simply swap the parent's text utility via the [&>svg]:text-* selector. The icon itself stays untouched.
Five variants, one pattern
Each Alert passes a Lucide icon as child. The variant class paints it via [&>svg]:text-*.
Icon inherits fg-secondary from the variant.
Tint follows the active accent token.
Pointing at semantic success token.
No prop on the icon — parent drives it.
Same Lucide import, different wrapper.
How it's wired
The selector lives on the wrapper's CVA — the icon stays prop-free.
const alertVariants = cva( "... [&>svg]:size-4", { variants: { variant: { default: "[&>svg]:text-fg-secondary", info: "[&>svg]:text-accent", success: "[&>svg]:text-success", warning: "[&>svg]:text-warning", destructive: "[&>svg]:text-destructive", }, }, }, );
<Alert variant="success"> <CheckCircle2 /> <AlertTitle>Saved</AlertTitle> </Alert>
// Wrong — hardcoded on the icon <Bell stroke="#3b82f6" className="size-5" /> // Right — parent drives color, icon inherits <div className="text-accent"> <Bell className="size-5" /> </div> // Or shorthand (same thing — Bell is just a span with currentColor SVG) <Bell className="size-5 text-accent" />
Usage
Never inline raw SVG. Always import from the right package. Never mix a brand logo into a component that the design system publishes (blocks only).
import { Search, Settings } from "lucide-react"; import { SiGithub, SiX } from "@icons-pack/react-simple-icons"; <Search className="size-4" /> <SiGithub className="size-4" />
pnpm add lucide-react @icons-pack/react-simple-icons