Skip to main content

IconButton

Overview​

The IconButton component renders a button that shows only an icon, and requires a name for it.

Reach for it where a button has room for a picture and no words: the edit, delete and share actions in a table row or on a card, a toolbar of view actions, a search or clear button beside an input, the menu or settings button in a navbar. Mantine calls it ActionIcon.

It's a Button underneath, so it takes every prop Button takes. What it adds is the icon and a name: the types require aria-label, or aria-labelledby in its place, since an icon gives a screen reader nothing to announce. Bulma keeps a button whose only child is an icon square at every size, and isRounded makes it a circle.

info

Bulma draws nearly all of it. IconButton brings two rules, in bestax.css and its variants, or in extras.css next to your own Bulma: one makes a rounded icon button round, and one keeps the text and ghost looks from underlining the glyph. Without them, isRounded gives Bulma's pill.


Import​

import { IconButton } from '@allxsmith/bestax-bulma';

Usage​

Basic Usage​

Pass the icon's name as icon and name the button for what it does.

<IconButton icon="trash" aria-label="Delete" />

Naming It​

aria-label is the name a screen reader announces and the word a voice control user says to press it. Name the action ("Delete"), not the picture ("Trash"). Where the name is already on the page, point aria-labelledby at it instead. The types take one or the other, never both, and a button with neither doesn't compile. For <IconButton icon="trash" />, tsc reports that no overload matches the call, and gives each overload's reason under it, ending in the missing aria-label (the long prop types are cut here):

error TS2769: No overload matches this call.
Overload 1 of 2, '(props: …): ReactElement<…> | null', gave the following error.
Type '{ icon: string; }' is not assignable to type 'IntrinsicAttributes & …'.
Property ''aria-label'' is missing in type '{ icon: string; }' but required in type 'IconButtonLabel'.
Overload 2 of 2, '(props: …): ReactElement<…> | null', gave the following error.
…

An empty or blank aria-label still compiles, so a development build warns about it.

<>
  <Paragraph id="archive-label" textSize="7">
    Archive this conversation
  </Paragraph>
  <IconButton icon="box-archive" aria-labelledby="archive-label" />
</>

The Icon​

icon takes an icon name, drawn from the library a ConfigProvider sets, or an Icon of your own, which renders as given, so it can carry its own variant or library. Any other node, such as an inline SVG or a component from an icon package, goes inside an Icon, which gives it the .icon wrapper Bulma keeps square. Or give the glyph as children, the way Mantine, MUI and Chakra take it: an <Icon> inside the button renders as given, and anything else goes inside an Icon, the same as with icon.

<Buttons>
  <IconButton icon="heart" aria-label="Favorite" />
  <IconButton
    icon={<Icon name="heart" variant="regular" />}
    aria-label="Add to favorites"
  />
  <IconButton
    icon={
      <svg viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor">
        <path d="M18 16a3 3 0 0 0-2.4 1.2L8.9 13.7a3 3 0 0 0 0-3.4l6.7-3.5A3 3 0 1 0 15 5l-6.7 3.5a3 3 0 1 0 0 7L15 19a3 3 0 1 0 3-3Z" />
      </svg>
    }
    aria-label="Share"
  />
  <IconButton aria-label="Settings">
    <Icon name="gear" />
  </IconButton>
</Buttons>

Draw an SVG with fill="currentColor", as above, so it takes the button's text color, in every theme and in forced colors.

Give the glyph one way. With both icon and children, it draws icon, leaves the children out and warns in development. With neither, such as an icon of cond ? 'trash' : '' that comes out empty, the button is an empty square, and that warns in development too. Text in children warns as well, since aria-label replaces it as the name: a button with words is a Button.

With a Badge​

To show a count or a dot on it, wrap it in a Badge, with overlap="circle" for a rounded one. Put what the badge says in the button's name too ("Notifications, 3 unread"), since a screen reader reaches the count on its own.

<Buttons p="2">
  <Badge content={3} overlap="circle">
    <IconButton isRounded icon="bell" aria-label="Notifications, 3 unread" />
  </Badge>
  <Badge content={12} overlap="square">
    <IconButton icon="envelope" aria-label="Inbox, 12 unread" />
  </Badge>
  <Badge dot color="success" overlap="circle">
    <IconButton isRounded icon="comment" aria-label="Messages, new" />
  </Badge>
</Buttons>

Colors​

color takes every color Button takes, and isLight, isOutlined and isInverted work as they do on Button. The colorless text and ghost looks suit a quiet toolbar or a card's corner. On a Button Bulma underlines those, which would run under a glyph, so IconButton leaves the underline off.

<>
  <Buttons>
    <IconButton color="primary" icon="plus" aria-label="Add" />
    <IconButton color="link" icon="pen" aria-label="Edit" />
    <IconButton color="info" icon="circle-info" aria-label="Details" />
    <IconButton color="success" icon="check" aria-label="Approve" />
    <IconButton color="warning" icon="flag" aria-label="Report" />
    <IconButton color="danger" icon="trash" aria-label="Delete" />
  </Buttons>
  <Buttons>
    <IconButton color="danger" isLight icon="trash" aria-label="Delete" />
    <IconButton color="link" isOutlined icon="download" aria-label="Download" />
    <IconButton color="text" icon="ellipsis" aria-label="More actions" />
    <IconButton color="ghost" icon="xmark" aria-label="Close" />
  </Buttons>
</>

Sizes and Circles​

size takes Button's sizes. With Bulma's defaults the buttons are 30, 40, 50 and 60 pixels square from small to large, the height of an input of the same size. isRounded makes each a circle of the same size.

<>
  <Buttons>
    <IconButton size="small" icon="pen" aria-label="Edit, small" />
    <IconButton icon="pen" aria-label="Edit" />
    <IconButton size="medium" icon="pen" aria-label="Edit, medium" />
    <IconButton size="large" icon="pen" aria-label="Edit, large" />
  </Buttons>
  <Buttons>
    <IconButton
      size="small"
      isRounded
      color="primary"
      icon="plus"
      aria-label="Add, small"
    />
    <IconButton isRounded color="primary" icon="plus" aria-label="Add" />
    <IconButton
      size="medium"
      isRounded
      color="primary"
      icon="plus"
      aria-label="Add, medium"
    />
    <IconButton
      size="large"
      isRounded
      color="primary"
      icon="plus"
      aria-label="Add, large"
    />
  </Buttons>
</>

With a Tooltip​

IconButton has no tooltip of its own. Wrap it in a Tooltip to add a description. A tooltip describes its trigger and doesn't name it, so give it something to add to the name ("Delete", described as "Moves it to the trash"), not the name again, which a screen reader would read twice.

<Buttons p="6">
  <Tooltip label="Moves it to the trash" position="bottom">
    <IconButton icon="trash" aria-label="Delete" />
  </Tooltip>
  <Tooltip label="Opens it for editing" position="bottom">
    <IconButton icon="pen" aria-label="Edit" />
  </Tooltip>
  <Tooltip label="Saves a copy as a PDF" position="bottom">
    <IconButton icon="download" aria-label="Download" />
  </Tooltip>
</Buttons>

Tooltip wraps its trigger in a <span>, so keep tooltips out of a joined row: Bulma squares off the corners between buttons by where each sits among its siblings, and a wrapped button is always the first of one.

A Row of Actions​

Several go in Buttons, and hasAddons joins them into one row. Give a joined row role="group" and an aria-label, so a screen reader names the set. An action that turns something on and off, like Bold, is a ToggleButton, which reports whether it's pressed. IconButton is also Buttons.IconButton, so the row can come from the one Buttons import.

<Buttons hasAddons role="group" aria-label="Zoom">
  <IconButton icon="minus" aria-label="Zoom out" />
  <IconButton icon="expand" aria-label="Fit to window" />
  <IconButton icon="plus" aria-label="Zoom in" />
</Buttons>

Beside an Input​

In a Field with hasAddons, it's the input's height at each size. type is button unless you say otherwise, so it never submits the form around it by accident. Pass type="submit" for a search button.

function example() {
  const [query, setQuery] = useState('');
  const [searched, setSearched] = useState('');

  return (
    <form
      onSubmit={event => {
        event.preventDefault();
        setSearched(query);
      }}
    >
      <Field hasAddons>
        <Control isExpanded>
          <Input
            type="search"
            aria-label="Search the docs"
            value={query}
            onChange={event => setQuery(event.target.value)}
          />
        </Control>
        <Control>
          <IconButton
            icon="xmark"
            aria-label="Clear"
            onClick={() => setQuery('')}
          />
        </Control>
        <Control>
          <IconButton
            type="submit"
            color="primary"
            icon="magnifying-glass"
            aria-label="Search"
          />
        </Control>
      </Field>
      <Paragraph>
        {searched ? `Searched for ${searched}.` : 'No search yet.'}
      </Paragraph>
    </form>
  );
}

Loading​

isLoading shows Bulma's spinner with the icon hidden behind it. The button keeps its size, its name and its focus, since it isn't disabled.

<Buttons>
  <IconButton isLoading icon="floppy-disk" aria-label="Save" />
  <IconButton
    isLoading
    isRounded
    color="primary"
    icon="floppy-disk"
    aria-label="Save"
  />
</Buttons>

Disabled​

isDisabled (or disabled) disables it, as on Button.

<IconButton isDisabled icon="trash" aria-label="Delete" />

as renders it as an <a> or a router's link component, as on Button, and the attributes and the ref follow. On an anchor, type passes through as the link's MIME type hint.

<IconButton
  as="a"
  href="https://bulma.io/documentation/elements/button/"
  target="_blank"
  rel="noreferrer"
  icon="arrow-up-right-from-square"
  aria-label="Bulma's button docs, in a new tab"
/>


Accessibility​

  • A name every time: an icon-only button's name is its text alternative (WCAG 1.1.1 and 4.1.2), so the types require aria-label or aria-labelledby, and a development build warns about an empty or blank one. Name the action, not the picture.
  • The name is the button's: its aria-label or aria-labelledby names it over anything inside it, so a name on the Icon is ignored, and there's no need to give one.
  • Tooltips describe: a Tooltip around it points the button's aria-describedby at itself, so the name and the description stay separate. Give the tooltip something to add, not the name again.
  • Keyboard and focus: it's a native <button>, so Enter and Space press it, with Bulma's focus style. isLoading leaves it focusable.
  • Target size: Bulma's smallest is 30 by 30 pixels, above the 24 by 24 WCAG 2.5.8 asks for.
  • Toggles are ToggleButton, which reports aria-pressed. Give an icon-only one an aria-label the same way.
  • Forced colors: a font icon, or an SVG drawn in currentColor, takes the system's text color, as a button's text does.

  • Button: The button it renders through, for a button with words.
  • ToggleButton: A button that stays pressed, for an action that turns something on and off.
  • Buttons: Lays out several, and hasAddons joins them into one row.
  • Tooltip: Adds a description to it.
  • Icon: The glyph inside it.
  • Delete: Bulma's small round close button, for dismissing a notification, a tag or a modal.
  • Helper Props: Bulma helper props for spacing, display, etc.

Additional Resources​


Props​

PropTypeDefaultDescription
asReact.ElementType'button'Render as a <button>, an <a>, or a custom component such as a router Link, as on Button.
iconReact.ReactNode—The glyph. A string is an Icon name, drawn from the icon library ConfigProvider sets. An <Icon> renders as given, so it can carry its own variant or library. Any other node, such as an SVG or a component from an icon package, goes inside an Icon, which gives it the .icon wrapper Bulma keeps square. It's decorative: the button's name says what it does. Give the glyph here or as children, not both: with both, it draws icon and warns in development. With neither, such as an empty string from cond ? 'trash' : '', the button is an empty square, and that warns in development too.
childrenReact.ReactNode—The glyph as children, the way Mantine, MUI and Chakra take it: an <Icon> renders as given, and any other node, such as an SVG, goes inside an Icon, as with icon. Left out when icon is given, which warns in development. Text warns too, since aria-label replaces it as the name: a button with text is a Button.
isRoundedbooleanfalseMakes the button a circle the size of its square. The circle comes from bestax's CSS, in bestax.css and its variants, or in extras.css next to your own Bulma. Without it, Bulma's is-rounded draws a pill a little wider than it is tall.
aria-labelledbystring—The id of the element whose text names the button, in place of aria-label.
aria-labelstring—The button's name: what a screen reader announces and what a voice control user says to press it. Required, or aria-labelledby in its place, since an icon has no text to name it by. Name the action ("Delete"), not the picture ("Trash"). An empty or blank one warns in development.
color'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger' | 'white' | 'light' | 'dark' | 'black' | 'text' | 'ghost'—Bulma color variant for the button. ghost renders a link-like button; text renders a minimal text-only button.
size'small' | 'normal' | 'medium' | 'large'—Size of the button.
isLightbooleanfalseApplies a lighter color variant.
isLoadingbooleanfalseDisplays a loading spinner, with everything inside the button hidden behind it, in forced colors (Windows' contrast themes) too. Under prefers-reduced-motion: reduce the spinner stops and stays drawn. Bulma alone hides only the label, so the rest needs bestax's CSS loaded.
isStaticbooleanfalseMakes the button non-interactive.
isFullwidthbooleanfalseMakes the button full-width.
isFullWidthbooleanfalseDeprecated. Use isFullwidth instead — isFullwidth wins if both are set. Makes the button full-width.
isOutlinedbooleanfalseApplies outlined styling (requires color).
isInvertedbooleanfalseApplies inverted styling (requires color).
isFocusedbooleanfalseApplies focused styling (visual only).
isActivebooleanfalseApplies active styling (visual only), with no aria-pressed. For a button that stays pressed, use ToggleButton.
isHoveredbooleanfalseApplies hovered styling (visual only).
isDisabledbooleanfalseApplies disabled styling.
classNamestring—Custom class name.
textColorBulma color | 'inherit' | 'current'—Text color helper.
bgColorBulma color | 'inherit' | 'current'—Background color helper.
typestring'button'On a <button>, 'button', 'submit' or 'reset', and 'button' unless you pass one of the other two, so it never submits a form by accident. With another as, whatever that element or component takes, passed through as given, such as an <a>'s MIME type.
refPolymorphicRef<React.ElementType>—Ref forwarded to the element as renders, typed from as.
...Remaining props of the element or component selected by as (default <button>) and Bulma helper props—See Helper Props

CSS & Sass Variables​

IconButton has no variables of its own. It's a Button underneath, so Button's variables style it, set the same way: on the button itself or through className. The rule that makes a rounded one a circle reads two of them, --bulma-button-padding-horizontal and --bulma-button-border-width, so a theme that changes those keeps it round.