Icon Button
Square icon-only button. Automatically wraps in a Tooltip when aria-label is provided.
| Prop | Type | Default |
|---|---|---|
| variant | "solid" | "outlined" | "ghost" | "minimal" | "solid" |
| color | "root" | "primary" | "secondary" | "accent" | "root" |
| size | "sm" | "md" | "lg" | "md" |
| aria-label | string | — |
| tooltipSide | "top" | "bottom" | "left" | "right" | "top" |
| loading | boolean | false |
| disabled | boolean | false |
Solid
default<IconButton variant="solid" color="root" aria-label="Menu">
<MenuIcon />
</IconButton>
<IconButton variant="solid" color="primary" aria-label="Share">
<ShareIcon />
</IconButton>
<IconButton variant="solid" color="secondary" aria-label="Theme">
<DarkModeIcon />
</IconButton>
<IconButton variant="solid" color="accent" aria-label="Close">
<CloseIcon />
</IconButton>Outlined
<IconButton variant="outlined" color="root" aria-label="Menu">
<MenuIcon />
</IconButton>
<IconButton variant="outlined" color="primary" aria-label="Share">
<ShareIcon />
</IconButton>
<IconButton variant="outlined" color="secondary" aria-label="Theme">
<DarkModeIcon />
</IconButton>
<IconButton variant="outlined" color="accent" aria-label="Close">
<CloseIcon />
</IconButton>Ghost
<IconButton variant="ghost" color="root" aria-label="Menu">
<MenuIcon />
</IconButton>
<IconButton variant="ghost" color="primary" aria-label="Share">
<ShareIcon />
</IconButton>
<IconButton variant="ghost" color="secondary" aria-label="Theme">
<DarkModeIcon />
</IconButton>
<IconButton variant="ghost" color="accent" aria-label="Close">
<CloseIcon />
</IconButton>Minimal
<IconButton variant="minimal" color="root" aria-label="Menu">
<MenuIcon />
</IconButton>
<IconButton variant="minimal" color="primary" aria-label="Share">
<ShareIcon />
</IconButton>
<IconButton variant="minimal" color="secondary" aria-label="Theme">
<DarkModeIcon />
</IconButton>
<IconButton variant="minimal" color="accent" aria-label="Close">
<CloseIcon />
</IconButton><IconButton size="sm" aria-label="Small">
<MenuIcon />
</IconButton>
<IconButton size="md" aria-label="Medium">
<MenuIcon />
</IconButton>
<IconButton size="lg" aria-label="Large">
<MenuIcon />
</IconButton><IconButton loading aria-label="Loading">
<MenuIcon />
</IconButton>
<IconButton variant="outlined" loading aria-label="Loading">
<MenuIcon />
</IconButton>
<IconButton variant="ghost" loading aria-label="Loading">
<MenuIcon />
</IconButton><IconButton disabled aria-label="Disabled">
<MenuIcon />
</IconButton>
<IconButton variant="outlined" disabled aria-label="Disabled">
<MenuIcon />
</IconButton>
<IconButton variant="ghost" disabled aria-label="Disabled">
<MenuIcon />
</IconButton>Tooltip
When aria-label is provided, the button is automatically wrapped in a Tooltip. Use tooltipSide to control placement.
<IconButton variant="ghost" aria-label="Open menu">
<MenuIcon />
</IconButton>
<IconButton variant="ghost" aria-label="Share" tooltipSide="bottom">
<ShareIcon />
</IconButton>import { IconButton } from "@/ui";
import { MenuIcon } from "@/ui/icons";
<IconButton aria-label="Menu">
<MenuIcon />
</IconButton>src/ui/IconButton.tsx
import { cn } from "@/utils";
import { ButtonHTMLAttributes, forwardRef } from "react";
import { Loader } from "./Loader";
import { Tooltip } from "./Tooltip";
export interface IconButtonProps extends Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
"color"
> {
variant?: "solid" | "outlined" | "ghost" | "minimal";
color?: "root" | "primary" | "secondary" | "accent";
size?: "sm" | "md" | "lg";
loading?: boolean;
tooltipSide?: "top" | "bottom" | "left" | "right";
}
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
function IconButton(
{
variant = "solid",
color = "root",
size = "md",
disabled = false,
loading = false,
tooltipSide,
className,
children,
...props
},
ref,
) {
const label = props["aria-label"];
const isDisabled = disabled || loading;
const button = (
<button
ref={ref}
aria-busy={loading || undefined}
className={cn(
"focus-visible:ring-primary inline-flex cursor-pointer items-center justify-center rounded-lg transition-colors focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
size === "sm" && "size-9",
size === "md" && "size-10",
size === "lg" && "size-11",
// solid
variant === "solid" && color === "root" && "bg-root-contrast text-root hover:bg-root-hover",
variant === "solid" && color === "primary" && "bg-primary text-primary-contrast hover:bg-primary-hover",
variant === "solid" && color === "secondary" && "bg-secondary text-secondary-contrast hover:bg-secondary-hover",
variant === "solid" && color === "accent" && "bg-accent text-accent-contrast hover:bg-accent-hover",
// outlined
variant === "outlined" && color === "root" && "border border-root-contrast text-root-contrast hover:text-root hover:bg-root-contrast",
variant === "outlined" && color === "primary" && "border border-primary text-primary hover:bg-primary hover:text-primary-contrast",
variant === "outlined" && color === "secondary" && "border border-secondary text-secondary hover:bg-secondary hover:text-secondary-contrast",
variant === "outlined" && color === "accent" && "border border-accent text-accent hover:bg-accent hover:text-accent-contrast",
// ghost
variant === "ghost" && color === "root" && "bg-root-ghost text-root-contrast hover:bg-root-ghost-hover",
variant === "ghost" && color === "primary" && "bg-primary-ghost text-primary hover:bg-primary-ghost-hover",
variant === "ghost" && color === "secondary" && "bg-secondary-ghost text-secondary hover:bg-secondary-ghost-hover",
variant === "ghost" && color === "accent" && "bg-accent-ghost text-accent hover:bg-accent-ghost-hover",
// minimal
variant === "minimal" && color === "root" && "text-root-contrast-subtle hover:bg-root-ghost hover:text-root-contrast",
variant === "minimal" && color === "primary" && "text-primary hover:bg-primary-ghost",
variant === "minimal" && color === "secondary" && "text-secondary hover:bg-secondary-ghost",
variant === "minimal" && color === "accent" && "text-accent hover:bg-accent-ghost",
className,
)}
disabled={isDisabled}
{...props}
>
{loading ? (
<Loader className="size-[1em] text-current!" aria-hidden="true" />
) : (
children
)}
</button>
);
return label ? <Tooltip label={label} side={tooltipSide}>{button}</Tooltip> : button;
},
);