Skip to content

Iconography

Lucide icons on a 24px grid, sized with the text they sit beside and colored by the component that holds them.

prfct uses Lucide, an open-source set of more than 1,500 consistent, 24px-grid outline icons. Components size and color icons for you; your job is to choose the right one — or none.

14pxSearch12px text
16pxSearch14px text
18pxSearch16px text
20pxSearch18px text
24pxSearch24px text
import { SearchIcon } from "lucide-react"

const pairs = [
  { size: 14, text: "text-xs", label: "12px text" },
  { size: 16, text: "text-sm", label: "14px text" },
  { size: 18, text: "text-base", label: "16px text" },
  { size: 20, text: "text-lg", label: "18px text" },
  { size: 24, text: "text-2xl", label: "24px text" },
]

export default function IconSizes() {
  return (
    <div className="flex flex-col gap-4">
      {pairs.map(({ size, text, label }) => (
        <div key={size} className="flex items-center gap-4">
          <span className="w-12 font-mono text-[0.6875rem] text-muted-foreground">
            {size}px
          </span>
          <span className={`flex items-center gap-2 ${text} text-foreground`}>
            <SearchIcon
              style={{ width: size, height: size }}
              aria-hidden="true"
            />
            Search
          </span>
          <span className="font-mono text-[0.6875rem] text-muted-foreground">
            {label}
          </span>
        </div>
      ))}
    </div>
  )
}

Sizing

Icons are sized to the text they accompany, not to a fixed size. The stroke scales with the icon, so a 16px icon beside 14px text has the same visual weight as the letters.

TextIconWhere
12–13px14pxExtra-small buttons, large badges, captions
14px16pxButtons, inputs, menu items — the default
16px18pxxl buttons, body text
18–20px20pxHeadings, empty states
24px+24pxHero moments only

Small and default badges are the one exception: their icons are 12px, close to the size of the text, so they fit an 18–20px pill.

Inside prfct components, don't size icons yourself. Buttons, menu items, inputs and badges size their icons with [&_svg] selectors. In buttons, menu items and inputs a size-* class on the icon overrides that choice, so add one only when you mean to leave the scale; badges ignore it.

// Correct — the button sizes and spaces the icon
<Button>
  <PlusIcon data-icon="inline-start" />
  New project
</Button>

Color

Icons inherit currentColor. Inside menus and inputs they default to muted-foreground and switch to foreground when the item is highlighted, so the label always leads. Status icons use their status step 11.

Guidelines

  • Pair icons with text. Icons alone are ambiguous. Use icon-only buttons only for universally understood actions (close, search, more) and always give them an aria-label and a tooltip.
  • One metaphor per action. Use the same icon for the same action everywhere: Trash2 always means delete.
  • Decorative icons are hidden. Icons next to a text label are decorative; Lucide renders aria-hidden="true" by default.
  • Don't mix icon families. Mixed stroke widths and corner styles look accidental. If you need a brand mark Lucide doesn't have, draw it on the same 24px grid with a 2px stroke.
DoIcon and label reinforce each other.
Don’tAn unlabeled icon forces people to guess — and screen readers announce nothing.