IconButton

Runs an action or toggles state from an icon in toolbars and table rows.

import { IconButton } from "@usefragments/ui";Source

Preview

import { Plus as PlusIcon } from '@phosphor-icons/react';

<IconButton aria-label="Add item">
  <PlusIcon aria-hidden />
</IconButton>

Usage

Use when

  • Toolbars and top bars where text buttons crowd
  • Row actions too narrow for a label
  • Toggle actions that show a pressed state

Avoid when

  • The action needs a visible label — use Button
  • It flips a persistent setting — use Switch
  • The icon is decorative — use Icon

Implementation notes

  • aria-label or aria-labelledby is required, every time
  • The only icon-only control: never a Button wrapping a lone icon
  • ghost for ordinary toolbar actions; soft when it needs a touch more weight
  • pressed only for real toggle state; it paints the selection wash and ring
  • xs for in-row actions; it still reaches the hit-area floor
  • Keep icon meaning obvious, or pair it with a Tooltip

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Examples

Variants

Transparent, then the neutral tint.

import { Plus as PlusIcon } from '@phosphor-icons/react';
import { Stack } from '@usefragments/ui';

<Stack direction="row" gap="sm" align="center">
  <IconButton aria-label="Add item" variant="ghost">
    <PlusIcon aria-hidden />

Sizes

xs for in-row actions, lg for primary chrome.

import { Plus as PlusIcon } from '@phosphor-icons/react';
import { Stack } from '@usefragments/ui';

<Stack direction="row" gap="sm" align="center">
  <IconButton aria-label="Add item" size="xs">
    <PlusIcon aria-hidden />

Toggle

A real toggle: the pressed state is owned by the caller and reported through aria-pressed.

import { PushPin as PinIcon } from '@phosphor-icons/react';

const [pinned, setPinned] = React.useState(false);

<IconButton aria-label="Pin item" pressed={pinned} onPressedChange={setPinned}>
  <PinIcon aria-hidden />

Disabled

Unavailable action, out of the tab order.

import { Trash as TrashIcon } from '@phosphor-icons/react';

<IconButton aria-label="Delete row" disabled>
  <TrashIcon aria-hidden />
</IconButton>

Pending

Work is running; the spinner takes the glyph's place.

import { ArrowsClockwise } from '@phosphor-icons/react';

<IconButton aria-label="Syncing" pending>
  <ArrowsClockwise aria-hidden />
</IconButton>

API reference

Component props
Prop
Type
Default
Description
childrenRequiredReact.ReactNodeNot setIcon element rendered inside the button
defaultPressedboolean | undefinedNot setInitial pressed state of an uncontrolled toggle
nativeButtonboolean | undefinedNot setWhether the rendered element is a native `<button>`. Inferred from an element passed to `render` (a `<button>`, or a component without `href`).
onPressedChange((pressed: boolean) => void) | undefinedNot setCalled with the next pressed state
pendingboolean | undefinedNot setWork is running: aria-busy, presses ignored, a spinner replaces the glyph after a second
pressedboolean | undefinedNot setControlled pressed state; any of pressed, defaultPressed or onPressedChange makes the button a toggle with aria-pressed
renderuseRender.RenderProp<Record<string, unknown>> | undefinedNot setReplace the rendered element, e.g. an anchor or a router link; a rendered anchor keeps link semantics and takes the look, pending and disabled behaviour
size"xs" | "sm" | "md" | "lg" | undefinedxssmmdlg"md"Square control on the one track: xs 24, sm 28, md 32, lg 40
valuestring | undefinedNot setForm value of the button, or the toggle's value inside a group.
variant"soft" | "ghost" | undefinedsoftghostghostghost is transparent at rest; soft is the neutral tint shared with Button soft
aria-labelstring | undefinedNot setAccessible action name when no labelled-by relationship is available
aria-labelledbystring | undefinedNot setID reference for an external accessible action label
disabledboolean | undefinedNot set—
onClickMouseEventHandler<HTMLButtonElement> | undefinedNot set—
type"button" | "submit" | "reset" | undefinedbuttonsubmitresetbutton—

Accessibility

  • Name the action, not the icon: "Delete row", not "Trash"
  • Pressed state is exposed via aria-pressed
  • Pending sets aria-busy
  • Hit target stays at least 24px even at size="xs"
  • ButtonsiblingUse Button when the action should have a visible text label or broader layout variants
  • IconcomplementaryIcon supplies the visual glyph rendered inside IconButton

Next steps