Propel

Icon Button

An icon-only button for compact, labeled actions like adding, editing, or closing.

An aria-label is required: icon-only buttons have no visible text, so the accessible name comes from it.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { AddOutline } from "@makeplane/propel/icons";

export default function BasicDemo() {
  return (
    <IconButton
      variant="primary"
      size="sm"
      aria-label="Add item"
      icon={<Icon icon={AddOutline} />}
    />
  );
}

Installation

import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";

Usage

import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { AddOutline } from "@makeplane/propel/icons";

export default function BasicDemo() {
  return (
    <IconButton
      variant="primary"
      size="sm"
      aria-label="Add item"
      icon={<Icon icon={AddOutline} />}
    />
  );
}

Examples

Tooltip

IconButton shows its aria-label in a tooltip on hover and keyboard focus by default. Set showTooltip={false} to hide it. For a different label, position, shortcut, or delay, wrap the button in Tooltip and set showTooltip={false} so only one popup appears.

Variants

Variant sets the visual weight and palette: primary, secondary, tertiary, and ghost, plus danger (solid fill) and danger-outline (bordered outline) for destructive actions.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { AddOutline, DeleteOutline } from "@makeplane/propel/icons";

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <IconButton
        variant="primary"
        size="sm"
        aria-label="Add member"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="secondary"
        size="sm"
        aria-label="Add member"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="tertiary"
        size="sm"
        aria-label="Add member"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="ghost"
        size="sm"
        aria-label="Add member"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="danger"
        size="sm"
        aria-label="Delete project"
        icon={<Icon icon={DeleteOutline} />}
      />
      <IconButton
        variant="danger-outline"
        size="sm"
        aria-label="Delete project"
        icon={<Icon icon={DeleteOutline} />}
      />
    </div>
  );
}

Sizes

size sets a square box — xs 20 / sm 24 / md 28 / lg 32 px. The glyph runs one step larger than a labelled control at the same rung (14 / 16 / 16 / 20 px), because there is no label to balance it. xs is under WCAG 2.5.8’s 24px floor and ships a transparent ring that brings the pointer target to 24×24 without moving a painted pixel — which is also why it is unavailable inside contiguous groups, where adjacent rings would intersect.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { AddOutline } from "@makeplane/propel/icons";

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <IconButton
        variant="secondary"
        size="xs"
        aria-label="Add label"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="secondary"
        size="sm"
        aria-label="Add label"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="secondary"
        size="md"
        aria-label="Add label"
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="secondary"
        size="lg"
        aria-label="Add label"
        icon={<Icon icon={AddOutline} />}
      />
    </div>
  );
}

Loading

The loading state shows a spinner, sets aria-busy, and blocks interaction.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { RefreshOutline } from "@makeplane/propel/icons";

export default function LoadingDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <IconButton
        variant="primary"
        size="sm"
        aria-label="Syncing workspace"
        loading
        icon={<Icon icon={RefreshOutline} />}
      />
      <IconButton
        variant="secondary"
        size="sm"
        aria-label="Syncing workspace"
        loading
        icon={<Icon icon={RefreshOutline} />}
      />
      <IconButton
        variant="tertiary"
        size="sm"
        aria-label="Syncing workspace"
        loading
        icon={<Icon icon={RefreshOutline} />}
      />
    </div>
  );
}

Disabled

disabled is the hard, non-focusable disabled state; unlike loading, it removes the button from the tab order.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { AddOutline, DeleteOutline } from "@makeplane/propel/icons";

export default function DisabledDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <IconButton
        variant="primary"
        size="sm"
        aria-label="Add item"
        disabled
        icon={<Icon icon={AddOutline} />}
      />
      <IconButton
        variant="danger-outline"
        size="sm"
        aria-label="Delete item"
        disabled
        icon={<Icon icon={DeleteOutline} />}
      />
    </div>
  );
}

API Reference

IconButton

The ready-made icon-only button: grafts Base UI's Button behavior onto the square IconButton box, filling it with the provided icon element and swapping in a spinner while loading. It can render as <a> via nativeButton={false} + render={<a href=… />} when needed. An aria-label is REQUIRED for the accessible name and appears in a tooltip by default.

PropTypeDefaultDescription
variant(required)"primary" | "secondary" | "tertiary" | "ghost" | "danger" | "danger-outline"—
size(required)"xs" | "sm" | "md" | "lg"—
icon(required)ReactNode—The single icon element to render, usually <Icon icon={...} />. The accessible name comes from aria-label.
disabledboolean—Hard, non-focusable native disabled state (loading stays focusable instead).
type"button" | "submit" | "reset"buttonThe button's form behavior.
aria-labelstring—Defines a string value that labels the current element. Required: icon-only buttons have no visible text, so they must be labeled. @see aria-labelledby.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>—Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.
nativeButtonboolean—Set false when render swaps the underlying tag away from <button> (e.g. an <a>): Base UI then adds role, tab focus, and Enter/Space activation as appropriate.
showTooltipbooleantrueShow the accessible label in a tooltip on hover (after Tooltip's 300ms) or focus (immediately). Set false when a caller supplies its own tooltip, including one with different timing.
loadingbooleanfalseShows a spinner in place of the icon, sets aria-busy, and makes the button non-interactive.