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.
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 a Link
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-labeloraria-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-labeloraria-labelledbynames it over anything inside it, so a name on theIconis ignored, and there's no need to give one. - Tooltips describe: a
Tooltiparound it points the button'saria-describedbyat 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.isLoadingleaves 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 reportsaria-pressed. Give an icon-only one anaria-labelthe 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.
Related Components
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, andhasAddonsjoins 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
- Bulma Button Documentation
- WAI-ARIA APG: Button Pattern
- WCAG Technique ARIA14: Using aria-label
- Storybook: IconButton Stories
Props
| Prop | Type | Default | Description |
|---|---|---|---|
as | React.ElementType | 'button' | Render as a <button>, an <a>, or a custom component such as a router Link, as on Button. |
icon | React.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. |
children | React.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. |
isRounded | boolean | false | Makes 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-labelledby | string | — | The id of the element whose text names the button, in place of aria-label. |
aria-label | string | — | 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. |
isLight | boolean | false | Applies a lighter color variant. |
isLoading | boolean | false | Displays 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. |
isStatic | boolean | false | Makes the button non-interactive. |
isFullwidth | boolean | false | Makes the button full-width. |
isFullWidth | boolean | false | Deprecated. Use isFullwidth instead — isFullwidth wins if both are set. Makes the button full-width. |
isOutlined | boolean | false | Applies outlined styling (requires color). |
isInverted | boolean | false | Applies inverted styling (requires color). |
isFocused | boolean | false | Applies focused styling (visual only). |
isActive | boolean | false | Applies active styling (visual only), with no aria-pressed. For a button that stays pressed, use ToggleButton. |
isHovered | boolean | false | Applies hovered styling (visual only). |
isDisabled | boolean | false | Applies disabled styling. |
className | string | — | Custom class name. |
textColor | Bulma color | 'inherit' | 'current' | — | Text color helper. |
bgColor | Bulma color | 'inherit' | 'current' | — | Background color helper. |
type | string | '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. |
ref | PolymorphicRef<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.