Home / Snippets / UI Components /

Icon-only button

Compact icon buttons with tooltip, focus ring, and disabled state — no text label needed.

Widely Supported
uino-js

Quick implementation

/* HTML: <button class="icon-btn" aria-label="Edit"><svg>...</svg></button> */

.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid oklch(0.3 0.02 260);
  border-radius: 0.5rem;
  background: oklch(0.19 0.02 260);
  color: oklch(0.93 0.01 260);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.icon-btn svg {
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-btn:hover {
  background: oklch(0.72 0.19 265);
  border-color: oklch(0.72 0.19 265);
  color: oklch(1 0 0);
}

.icon-btn:focus-visible {
  outline: 2px solid oklch(0.72 0.19 265);
  outline-offset: 2px;
}

.icon-btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.icon-btn[disabled]:hover {
  background: oklch(0.19 0.02 260);
  border-color: oklch(0.3 0.02 260);
  color: oklch(0.93 0.01 260);
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building a design system component library.

Goal: An icon-only button that is fully accessible, with hover/focus states and an optional tooltip — no JavaScript for styling.

Technical constraints:
- Use a native <button> element with aria-label for the accessible name.
- Size the button at 2.75rem × 2.75rem with inline-flex centering.
- SVG icon inherits color via currentColor on stroke.
- Hover state swaps background to the accent color with a 0.2s transition.
- Use :focus-visible (not :focus) for the keyboard focus ring with outline-offset.
- Use oklch() for all color values, not hex or rgba().

Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept icon (ReactNode), ariaLabel (string), disabled (boolean), onClick handler, and optional tooltip (string) props.

Edge cases to handle:
- Disabled state: opacity 0.4, cursor not-allowed, hover styles suppressed.
- Tooltip must not clip outside the viewport on edge-positioned buttons.
- Screen readers must announce the aria-label, not the tooltip text.
- Touch targets must meet WCAG 2.2 minimum of 24×24px (2.75rem exceeds this).

Return HTML + CSS.

Why this matters in 2026

Toolbars, action menus, and compact UIs rely heavily on icon-only buttons. Without a visible text label, accessibility depends entirely on aria-label and a discoverable tooltip. Modern CSS handles the hover-reveal tooltip, focus ring via :focus-visible, and disabled state without a single line of JavaScript — making the component lighter, faster, and easier to maintain across frameworks.

The logic

The button uses inline-flex with fixed dimensions to center the SVG icon. The SVG inherits its color through stroke: currentColor, so changing the button's color property on hover recolors the icon automatically. The tooltip is an absolutely-positioned child that transitions opacity from 0 to 1 on :hover and :focus-visible. The disabled attribute reduces opacity and overrides hover styles to prevent visual feedback on non-interactive elements.

Accessibility & performance

Every icon-only button must have an aria-label — without it, screen readers announce nothing or read the SVG markup. The tooltip is purely decorative (not role="tooltip") since the aria-label already provides the accessible name. Using :focus-visible instead of :focus ensures the outline only appears for keyboard navigation, not mouse clicks. The transition targets only background, border-color, and color — all compositor-friendly properties that avoid layout thrashing.