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 withasChild. - Labels
labelis required and becomesaria-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, notdisabled: it keeps its focus and tab stop, and refuses presses until the work ends.
Keyboard
| Key | Behaviour |
|---|---|
| Enter | Activates the button. |
| Space | Activates the button. |
| Escape | With tooltip, closes the tooltip. |
API
IconButton
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | string | What the button does, in words: its accessible name, and the tooltip's text. Required. | |
asChild | boolean | Render the child element instead (a link), with the button's look and behaviour merged onto it. | |
loading | boolean | Shows 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. | |
raised | boolean | null | Adds the raised shadow. | |
rounded | boolean | null | A circle. | |
severity | "success" | "info" | "warning" | "help" | "danger" | "contrast" | null | The 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. | |
tooltip | boolean | false | Shows label in a tooltip on hover and keyboard focus. |
tooltipSide | "top" | "bottom" | "left" | "right" | top | The tooltip's side: top (default), right, bottom or left. |
variant | "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | ghost | The 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.
| Token | Used for |
|---|