affectUI › Components › Buttons
Icon Button
A square button that is only an icon – ghost, outlined or solid, two sizes, a pressed state for toggles and its label as the tooltip.
Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/icon-button.json · Import from @/components/ui/icon-button
When to use
- Toolbar tools whose icon says it all: search, filter, sort, more
- Actions under a message or on a row: copy, like, regenerate – as quiet ghost buttons
- A tool that stays on, like pinned only or a side panel – pressed makes it a toggle
When not to use
- When the icon alone isn't clear, or for the main action of a page – use the Button with a label
- Choosing one of a few views (grid or list) – use the Segmented Control
- On or off settings that are saved – use the Switch
Usage
import { useState } from "react";
import { FilterHorizontalIcon, PinIcon, PlusSignIcon } from "@hugeicons/core-free-icons";
import { IconButton } from "@/components/ui/icon-button";
export function RunsToolbar({ onNew }: { onNew: () => void }) {
const [pinned, setPinned] = useState(false);
return (
<div style={{ display: "flex", gap: 6 }}>
<IconButton variant="secondary" icon={FilterHorizontalIcon} label="Filter" />
<IconButton
variant="secondary"
icon={PinIcon}
label="Pinned runs only"
pressed={pinned}
onClick={() => setPinned((p) => !p)}
/>
<IconButton variant="primary" icon={PlusSignIcon} label="New run" onClick={onNew} />
</div>
);
}Props
| Name | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | HugeIcon | The glyph – an inline SVG or image, or a Hugeicons icon object, which is drawn inline at a 1.75 stroke. It takes the button's color | |
label | string | Required. What it does – the accessible name (aria-label) and the tooltip | |
variant | "ghost" | "secondary" | "primary" | "ghost" | ghost: just the icon, a soft wash on hover · secondary: white with a 1px edge, for toolbars · primary: the dark gradient of the primary Button |
size | "sm" | "md" | "md" | 32 × 32 with a 16px icon, or 40 × 40 with an 18px icon – the heights of the Button |
pressed | boolean | Makes it a toggle: sets aria-pressed and keeps the gray layer while true. Leave it out for a plain button | |
title | string | label | The tooltip; pass "" to leave it out, e.g. when a custom tooltip wraps the button |
...button props | ComponentProps<"button"> | onClick, disabled, type, aria-expanded, ref and the rest go to the real <button> |
Keyboard
- Enter / Space: Clicks it – or flips it when it's a toggle
- Tab: Moves to the next control
Accessibility
- A real <button>; the label is its aria-label, so screen readers never hear just "button"
- With pressed it's announced as a toggle button, pressed or not pressed
- The icon is hidden from screen readers
- The ring shows only under keyboard focus (:focus-visible), never after a click
Responsive
- Touch screens keep the look and get an invisible ring around it that takes the tap – the small size grows to 42 × 42
- Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"
Requirements
- React 19
- Optional: @hugeicons/core-free-icons – its icon objects can be passed straight to icon
Related
- Button – Primary, secondary, ghost and destructive buttons with icons, sizes and a shimmering loading state
- Segmented Control – Switch between two to six options – a raised pill or underline that glides, stretches a little and can be dragged across, with icons, counts and badges
- Message Actions – The quiet bar under an AI message – copy turns into a check, regenerate spins and unfolds a version switcher, thumbs pop and ask why, read aloud becomes a tiny equalizer
- Reaction Bar – Like, comment, repost and share in one pill – icons pop and fill, counts roll in digit by digit