Skip to the content

ComponentsButton

Icon button

A square button with an icon and no text, which cannot be left without a name.

Import

import { IconButton } from "@booleanpress/ui/icon-button"

Usage

IconButton is a Button with a square size and a required label: TypeScript refuses an icon button without one. The label becomes its accessible name, and with tooltip it shows on hover and keyboard focus too.

import { IconButton } from "@booleanpress/ui/icon-button"
import { PencilIcon } from "lucide-react"

export function EditAction() {
  return (
    <IconButton label="Edit mailer" tooltip>
      <PencilIcon />
    </IconButton>
  )
}

The icon is its child. It takes Button's variant (ghost by default), severity, rounded, raised, loading and disabled. size is xs 24 px, sm 28 px, default 36 px or lg 42 px square, with 12, 12, 14 and 16 px icons; left out, it follows the provider's controlSize. tooltipSide moves the tooltip.

Examples

Basic

Text, outlined and danger icon buttons, each named by label.

import { PencilIcon, SettingsIcon, TrashIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonBasic() {
  return (
    <div className="flex gap-2">
      <IconButton label="Edit mailer">
        <PencilIcon />
      </IconButton>
      <IconButton label="Mailer settings" variant="outline">
        <SettingsIcon />
      </IconButton>
      <IconButton label="Delete mailer" variant="default" severity="danger">
        <TrashIcon />
      </IconButton>
    </div>
  )
}

Severities and variants

Each severity as a solid, outlined and text icon button.

import { BellIcon, CheckIcon, HeartIcon, InfoIcon, SearchIcon, TriangleAlertIcon, XIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

const SEVERITIES = [
  { severity: undefined, label: "Search", icon: <SearchIcon /> },
  { severity: "success", label: "Approve", icon: <CheckIcon /> },
  { severity: "info", label: "Details", icon: <InfoIcon /> },
  { severity: "warning", label: "Notifications", icon: <BellIcon /> },
  { severity: "help", label: "Favourite", icon: <HeartIcon /> },
  { severity: "danger", label: "Reject", icon: <XIcon /> },
  { severity: "contrast", label: "Warnings", icon: <TriangleAlertIcon /> },
] as const

export default function IconButtonSeverities() {
  return (
    <div className="flex flex-col gap-3">
      {(["default", "outline", "ghost"] as const).map((variant) => (
        <div key={variant} className="flex flex-wrap gap-2">
          {SEVERITIES.map(({ severity, label, icon }) => (
            <IconButton key={label} label={`${label} (${variant})`} variant={variant} severity={severity}>
              {icon}
            </IconButton>
          ))}
        </div>
      ))}
    </div>
  )
}

Sizes

xs, sm, default and lg: 24, 28, 36 and 42 px.

import { RefreshCwIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonSizes() {
  return (
    <div className="flex items-center gap-2">
      {(["xs", "sm", "default", "lg"] as const).map((size) => (
        <IconButton key={size} label={`Refresh (${size})`} size={size} variant="outline">
          <RefreshCwIcon />
        </IconButton>
      ))}
    </div>
  )
}

With tooltip

tooltip shows the label on hover and focus; tooltipSide places it.

import { CopyIcon, DownloadIcon, ShareIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonWithTooltip() {
  return (
    <div className="flex gap-2">
      <IconButton label="Copy message ID" tooltip>
        <CopyIcon />
      </IconButton>
      <IconButton label="Download the report" tooltip tooltipSide="bottom">
        <DownloadIcon />
      </IconButton>
      <IconButton label="Share with the organisation" tooltip tooltipSide="right">
        <ShareIcon />
      </IconButton>
    </div>
  )
}

Rounded

rounded makes a circle; raised adds the shadow.

import { CheckIcon, PlusIcon, XIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonRounded() {
  return (
    <div className="flex gap-2">
      <IconButton label="Add recipient" rounded variant="default">
        <PlusIcon />
      </IconButton>
      <IconButton label="Accept" rounded variant="outline" severity="success">
        <CheckIcon />
      </IconButton>
      <IconButton label="Decline" rounded variant="default" severity="danger" raised>
        <XIcon />
      </IconButton>
    </div>
  )
}

Loading

loading puts the spinner in place of the icon and refuses presses while the work runs; the button keeps its focus.

import * as React from "react"
import { RefreshCwIcon } from "lucide-react"
import { IconButton } from "@booleanpress/ui/icon-button"

export default function IconButtonLoading() {
  const [loading, setLoading] = React.useState(false)

  const refresh = () => {
    setLoading(true)
    window.setTimeout(() => setLoading(false), 1500)
  }

  return (
    <div className="flex gap-2">
      <IconButton label="Refresh the log" variant="outline" loading={loading} onClick={refresh}>
        <RefreshCwIcon />
      </IconButton>
      <IconButton label="Refreshing" variant="default" loading>
        <RefreshCwIcon />
      </IconButton>
    </div>
  )
}

Accessibility

Semantics
A native button, or the element passed with asChild.
Labels
label is required and becomes aria-label. The tooltip repeats it on screen only: it is not added as a description, so a screen reader does not read the label twice.
Focus
It is in the tab order unless disabled; the focus outline shows on keyboard focus. With tooltip, focus opens the tooltip at once.
Known limits
  • A disabled icon button cannot be focused or hovered, so its tooltip cannot open. Say near it why it is disabled.
  • A loading icon button is aria-disabled, not disabled: it keeps its focus and tab stop, and refuses presses until the work ends.

Keyboard

Keyboard
KeyBehaviour
EnterActivates the button.
SpaceActivates the button.
EscapeWith tooltip, closes the tooltip.

API

IconButton

IconButton props
PropTypeDefaultDescription
labelrequiredstringWhat the button does, in words: its accessible name, and the tooltip's text. Required.
asChildbooleanRender the child element instead (a link), with the button's look and behaviour merged onto it.
loadingbooleanShows the spinner in place of the leading icon, sets aria-busy and aria-disabled and ignores presses (a click, Enter, Space, a form's submission), while the button keeps its focus and its place in the tab order. With asChild the child (a link) gets the same.
raisedboolean | nullAdds the raised shadow.
roundedboolean | nullA circle.
severity"success" | "info" | "warning" | "help" | "danger" | "contrast" | nullThe colour of the default, outline, ghost and link variants: success, info, warning, help, danger or contrast.
size"default" | "xs" | "sm" | "lg"24 px xs, 28 px sm, 36 px default, 42 px lg. The provider's controlSize when left out.
tooltipbooleanfalseShows label in a tooltip on hover and keyboard focus.
tooltipSide"top" | "bottom" | "left" | "right"topThe tooltip's side: top (default), right, bottom or left.
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | nullghostThe emphasis, as Button's: ghost by default, or default, outline, secondary, destructive, link.

Also exported: IconButtonProps, a TypeScript type; IconButtonSize, a TypeScript type.

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="icon-button" (IconButton).

Theming

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for