Icons
Phosphor Icons - a flexible, MIT-licensed family with 6 weights per glyph and over 1 400 icons. Always import from /ssr in Server Components; the bare import is for 'use client' files only.
Import#
Phosphor ships two barrels. The /ssr barrel is tree-shaken and safe to use in any environment - Server Components, Edge, Node. The bare @phosphor-icons/react import registers a React context for the default weight, which requires a client boundary. Default to /ssr everywhere and only switch to the bare import inside "use client" files that need the context API.
// ✓ Server Components and most files - default to this
import { Bell, Star, ArrowRight } from "@phosphor-icons/react/ssr"
// ✓ Client Components that need the context API (e.g. global weight)
"use client"
import { Bell, Star, ArrowRight } from "@phosphor-icons/react"
// ✗ Never import the whole package
import * as Icons from "@phosphor-icons/react"Sizes#
Icons use Tailwind’s size-* utility (shorthand for width + height). Components apply a default of size-4 to any unclassed svg child - only override when the context calls for a different size.
size-3
12px
tight inline, badge counters
size-3.5
14px
separators, tag icons
size-4
16px
default - most components
size-5
20px
emphasis, standalone labels
size-6
24px
display, empty states
Weights#
Every Phosphor icon ships in six weights. Pass the weight prop - no separate import per style. bold is the default in Koala UI - it holds its weight at small sizes and reads as deliberate next to our type. Use fill for selected / active states, and drop to regular or light when you want a softer, more delicate mark.
// Pass weight directly - no separate import
<Star weight="bold" /> {/* default */}
<Star weight="fill" /> {/* selected state */}
<Star weight="regular" /> {/* softer, lighter mark */}Color#
Icons inherit the current text color - set text-* on the icon or any ancestor. Never hardcode a hex value; always use a semantic token so the icon re-themes correctly across all four Koala themes.
Accessibility#
Most icons are decorative - they reinforce meaning already conveyed by adjacent text, so screen readers should skip them entirely. Interactive icons (icon-only buttons, links) need an accessible label so a keyboard or AT user knows what they do.
// ✓ Decorative icon alongside text - silence it
<button>
<ArrowRight aria-hidden />
Continue
</button>
// ✓ Icon-only - label the interactive element, not the icon
<button aria-label="Go to next step">
<ArrowRight aria-hidden />
</button>
// ✓ Icon-only with visible tooltip - sr-only is redundant, but keep aria-label
// so AT users get the label without waiting for the tooltip to appear
<button aria-label="Notifications">
<Bell aria-hidden />
</button>
// ✗ aria-label on the icon itself - AT reads "Go to next step" then "image"
<button>
<ArrowRight aria-label="Go to next step" />
</button>In context#
Button, Badge, and Breadcrumb all size icons automatically via [&_svg] selectors - you rarely need to pass a size class when composing with those components.
import { ArrowRight, Plus, Check } from "@phosphor-icons/react/ssr"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
// Button sizes the svg to size-4 automatically
<Button><Plus /> New project</Button>
<Button variant="outline">Continue <ArrowRight /></Button>
// Badge sizes the svg to match its own size step
<Badge variant="success"><Check /> Deployed</Badge>