Icon button
An icon only button with a required accessible label and a tooltip that shows it.
npx shadcn@latest add https://ui.goodtabs.co/r/icon-button.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/icon-button
Ghost, outline and default variants. Hover or focus to see the label.
Sizes
The four icon sizes from shadcn's Button: 24, 28, 32 and 36 pixels.
Without a tooltip
12 new resources added this week
A dismiss button in a notice. The label stays as the accessible name.
Usage
import { IconButton } from "@/components/ui/icon-button"
import { PencilIcon } from "lucide-react"
<IconButton label="Edit" variant="outline">
<PencilIcon />
</IconButton>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | Accessible name. Becomes the aria-label and the tooltip text. | |
| tooltip | boolean | true | Show the label in a tooltip on hover and focus. |
| size | Button size | "icon-sm" | Any Button size. The icon sizes are icon-xs, icon-sm, icon and icon-lg. |
| variant | Button variant | "default" | Any shadcn Button variant. Ghost suits toolbars, outline suits rows. |
| children | ReactNode | The icon, usually one lucide icon. It is sized by the button. |
Source
icon-button/icon-button.tsx
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
type IconButtonProps = Omit<React.ComponentProps<typeof Button>, "aria-label"> & {
/** Accessible name. Becomes the aria-label and the tooltip text. */
label: string;
/** Show the label in a tooltip on hover and focus. */
tooltip?: boolean;
};
/**
* A button that shows one icon and always says what it does.
* The label is the accessible name; the tooltip shows it to everyone else.
*/
export function IconButton({
label,
tooltip = true,
size = "icon-sm",
type = "button",
children,
...props
}: IconButtonProps) {
const button = (
<Button type={type} size={size} aria-label={label} data-slot="icon-button" {...props}>
{children}
</Button>
);
if (!tooltip) return button;
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent>{label}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
For AI agents
Hand your agent this page, the markdown at /components/icon-button/prompt.md, or the Copy AI prompt button above.
When to use
- Row actions, card corners and toolbars where a text label would crowd the layout
- Any button whose icon is common enough to read on its own: close, edit, more, add
- When you want the tooltip and the accessible name to always agree
When not to use
- For primary actions on a page: those get a text label
- For an icon whose meaning is not obvious; use a Button with text and an icon instead
- When a toggle is needed; use Toggle or aria-pressed on a plain Button
Agent notes
- It extends shadcn's Button, so every Button prop and variant applies; size defaults to icon-sm (28px)
- label is required and is the accessible name; never pass aria-label separately
- Each button wraps its own TooltipProvider, so it works without a provider at the root
- Pass tooltip={false} when the button sits where a tooltip would get in the way, such as in a dense list