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

NameTypeDefaultDescription
iconReactNode | HugeIconThe 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
labelstringRequired. 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
pressedbooleanMakes it a toggle: sets aria-pressed and keeps the gray layer while true. Leave it out for a plain button
titlestringlabelThe tooltip; pass "" to leave it out, e.g. when a custom tooltip wraps the button
...button propsComponentProps<"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