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.

example.tsxTSX
// ✓ Server Components and most files - default to thisimport { 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 packageimport * 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

size-3.5

14px

size-4

16px

size-5

20px

size-6

24px

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.

thin
light
regular
bold
fill
duotone
example.tsxTSX
// 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.

foreground
muted-fg
primary
destructive
success
warning
info

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.

a11y.tsxTSX
// ✓ 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.

usage.tsxTSX
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>