Icon Button

A square button with one icon and no label, for toolbars and dense rows. It is the Button component at an icon size, so it has the same variants, tones, states and radius.

Installation

Icon Button is part of the button item, so installing Button installs it.

bash
npx @smicolon/fasla-ui@latest add button

Run init once per project before adding components. See Installation.

Preview

Variants

Four variants, as in Figma's Icon Button: default, soft, outline and ghost, each in the three tones. There is no link icon button: an icon on its own does not read as a link.

default
soft
outline
ghost

Sizes

Four square sizes: icon-xs is 26px, icon-sm 32px, icon-md 36px and icon-lg 40px. The icon is 16px at every size.

States

Hover, pressed and keyboard focus are real CSS states, as on Button. Filled icon buttons fade; soft, outline and ghost ones deepen their tint. Disabled dims every variant to half opacity.

Enabled
Disabled

Accessible name

An icon has no text a screen reader can read, so every icon button needs a name: an aria-label, or text inside it that you hide visually. In development, the button warns in the console when it has none. To show the name on hover too, wrap the button in a tooltip.

Accessibility

  • Give every icon button an aria-label. The button warns in development when it has no name.
  • The icon is decorative. Hide it from assistive technology with aria-hidden if your icon library does not.
  • Keyboard focus draws a 2px ring, 2px outside the button, in the ring colour.
  • At icon-xs the button is 26px square. WCAG 2.5.8 asks for at least 24px, so every size passes.

Props

PropTypeDefaultDescription
tone"primary" | "secondary" | "destructive"—The colour within the type. Each variant has its own default.
variant"default" | "outline" | "ghost" | "soft""default"The type: filled, outline, ghost or soft.
size"icon-xs" | "icon-sm" | "icon-md" | "icon-lg"—The icon sizes: square, with one 16px icon.
aria-labelstring—The name a screen reader reads. Required unless the button has visually hidden text.
radius"standard" | "rounded""standard"The corners: slightly rounded, or a circle.
loadingbooleanfalseReplaces the icon with a spinner and disables the button.
disabledbooleanfalseDisables the button.
classNamestring—Extra classes for the component.

Kept for shadcn compatibility: secondary and destructive still work as variant values (Solid in those tones), and icon as a size value (the same as icon-md).

Usage

Icon button

tsx
import { PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"

export function AddButton() {
  return (
    <Button size="icon-md" aria-label="Add">
      <PlusIcon aria-hidden="true" strokeWidth={1.5} />
    </Button>
  )
}

A toolbar of icon buttons

tsx
import { PencilIcon, Trash2Icon, XIcon } from "lucide-react"
import { Button } from "@/components/ui/button"

export function RowActions() {
  return (
    <div className="flex items-center gap-1">
      <Button size="icon-sm" variant="ghost" aria-label="Edit">
        <PencilIcon aria-hidden="true" strokeWidth={1.5} />
      </Button>
      <Button size="icon-sm" variant="ghost" tone="destructive" aria-label="Delete">
        <Trash2Icon aria-hidden="true" strokeWidth={1.5} />
      </Button>
      <Button size="icon-sm" variant="ghost" aria-label="Close">
        <XIcon aria-hidden="true" strokeWidth={1.5} />
      </Button>
    </div>
  )
}