Components
Icon Button
Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.
---import { IconButton } from "@opui/astro"---
<IconButton aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path></svg ></IconButton>
<IconButton aria-label="Edit"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path></svg ></IconButton><button aria-label="Delete" class="ui-icon-button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path> </svg></button><button aria-label="Edit" class="ui-icon-button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path> </svg></button>Variants
Icon buttons come in three variants: outlined, tonal and filled.
---import { IconButton } from "@opui/astro"---
<div style="display: flex; gap: 1rem; flex-wrap: wrap;"> <IconButton variant="outlined" aria-label="Outlined"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton> <IconButton variant="tonal" aria-label="Tonal"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton> <IconButton variant="filled" aria-label="Filled"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton>
<IconButton color="primary" variant="outlined" aria-label="Primary Outlined"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton> <IconButton color="primary" variant="tonal" aria-label="Primary Tonal"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton> <IconButton color="primary" variant="filled" aria-label="Primary Filled"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"></path></svg > </IconButton></div><div style="display: flex; gap: 1rem; flex-wrap: wrap"> <button aria-label="Outlined" class="ui-icon-button ui-outlined"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button> <button aria-label="Tonal" class="ui-icon-button ui-tonal"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button> <button aria-label="Filled" class="ui-icon-button ui-filled"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button> <button aria-label="Primary Outlined" class="ui-icon-button ui-outlined ui-primary" > <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button> <button aria-label="Primary Tonal" class="ui-icon-button ui-tonal ui-primary"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button> <button aria-label="Primary Filled" class="ui-icon-button ui-filled ui-primary" > <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <path d="M12 5v14M5 12h14"></path> </svg> </button></div>Colors
Pass color to apply a brand or destructive color: primary or critical. The default is a neutral gray.
---import { IconButton } from "@opui/astro"---
<IconButton color="primary" variant="filled" aria-label="Primary"> <svg fill="none" height="20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg" ><path d="M5 12h14"></path><path d="M12 5v14"></path></svg ></IconButton><IconButton color="critical" variant="filled" aria-label="Critical"> <svg fill="none" height="20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg" ><path d="M5 12h14"></path><path d="M12 5v14"></path></svg ></IconButton><button aria-label="Primary" class="ui-icon-button ui-filled ui-primary"> <svg fill="none" height="20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg" > <path d="M5 12h14"></path> <path d="M12 5v14"></path> </svg></button><button aria-label="Critical" class="ui-icon-button ui-filled ui-critical"> <svg fill="none" height="20" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="20" xmlns="http://www.w3.org/2000/svg" > <path d="M5 12h14"></path> <path d="M12 5v14"></path> </svg></button> aria-label?
Yes! In order for screen readers to understand what the button is for we
can add
Read more: Icon Button accessibility.
aria-label to the <button> element.
Read more: Icon Button accessibility.
Sizes
Make the button smaller with the .ui-small modifier.
---import { IconButton } from "@opui/astro"---
<IconButton size="small" aria-label="Edit"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path></svg ></IconButton>
<IconButton aria-label="Edit"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path></svg ></IconButton><button aria-label="Edit" class="ui-icon-button ui-small"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path> </svg></button><button aria-label="Edit" class="ui-icon-button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M21.65 3.434a4.889 4.889 0 1 1 6.915 6.914l-.902.901l-6.914-6.914zM19.335 5.75L4.357 20.73a3.7 3.7 0 0 0-1.002 1.84l-1.333 6.22a1 1 0 0 0 1.188 1.188l6.22-1.333a3.7 3.7 0 0 0 1.84-1.002l14.98-14.98z" ></path> </svg></button>Disabled
Prevent user interaction by adding the disabled attribute.
---import { IconButton } from "@opui/astro"---
<div style="display: flex; gap: 1rem; flex-wrap: wrap;"> <IconButton disabled aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path></svg > </IconButton>
<IconButton disabled variant="outlined" aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path></svg > </IconButton>
<IconButton disabled variant="tonal" aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path></svg > </IconButton>
<IconButton disabled variant="filled" aria-label="Delete"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" ><path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path></svg > </IconButton></div><div style="display: flex; gap: 1rem; flex-wrap: wrap"> <button disabled aria-label="Delete" class="ui-icon-button"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path> </svg> </button> <button disabled aria-label="Delete" class="ui-icon-button ui-outlined"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path> </svg> </button> <button disabled aria-label="Delete" class="ui-icon-button ui-tonal"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path> </svg> </button> <button disabled aria-label="Delete" class="ui-icon-button ui-filled"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" > <path fill="currentColor" d="M13.5 6.5V7h5v-.5a2.5 2.5 0 0 0-5 0m-2 .5v-.5a4.5 4.5 0 1 1 9 0V7H28a1 1 0 1 1 0 2h-1.508L24.6 25.568A5 5 0 0 1 19.63 30h-7.26a5 5 0 0 1-4.97-4.432L5.508 9H4a1 1 0 0 1 0-2zm2.5 6.5a1 1 0 1 0-2 0v10a1 1 0 1 0 2 0zm5-1a1 1 0 0 0-1 1v10a1 1 0 1 0 2 0v-10a1 1 0 0 0-1-1" ></path> </svg> </button></div>Accessibility
To have an accessible label you can choose between three approaches.
| Variant | Usage in Icon Button component |
|---|---|
Add a aria-label on the <button> element | Default behavior. |
Provide a label inside the <button> element | Not used (but possible with the .ui-sr-only util). |
Have a visible label that you reference with aria-labelledby | Not used. |
Anatomy
- Container:
<button class="ui-icon-button"> - Icon:
<svg> - Label text:
<button aria-label="">
API
| Prop | Type | Default | Description |
|---|---|---|---|
as | any | "button" (or "a" if href present) | The underlying HTML element. |
href | string | - | Renders as an tag if an href is provided. |
size | "small" | - | The size of the icon button. |
color | "critical", "primary" | - | The color of the icon button. Default is a neutral gray. |
variant | "outlined" | "tonal" | "filled" | - | The style of the icon button. |
disabled | boolean | - | Whether the icon button is disabled. |
Browser support
Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.
See also the full browser support guide.
Installation
@layer components.root { :where(.ui-icon-button) { --_color: light-dark(var(--color-16), var(--color-1)); --_color-contrast: light-dark(var(--color-1), var(--color-16)); --_color-tonal: var(--surface-tonal);
--_accent: var(--_color); --_accent-contrast: var(--_color-contrast); --_accent-tonal: var(--_color-tonal); --_accent-tonal-contrast: var(--_color); --_default-hover-bg-color: light-dark( oklch(from var(--_accent) l 0.01 h / 10%), oklch(from var(--_accent) l 0.01 h / 20%) ); --_default-active-bg-color: light-dark( oklch(from var(--_accent) l 0.06 h / 20%), oklch(from var(--_accent) l 0.06 h / 30%) ); --_filled-hover-bg-color: light-dark( oklch(from var(--_accent) calc(l + 0.2) c h), oklch(from var(--_accent) calc(l - 0.1) c h) ); --_filled-active-bg-color: light-dark( oklch(from var(--_accent) calc(l + 0.3) c h), oklch(from var(--_accent) calc(l - 0.15) c h) ); --_outlined-active-bg-color: var(--_filled-active-bg-color);
--_bg-color: transparent; --_border-color: transparent; --_border-width: var(--border-size-1); --_text-color: inherit;
align-items: center; aspect-ratio: 1; background-color: var(--_bg-color); block-size: var(--size-6); border: var(--_border-width) solid var(--_border-color); border-radius: var(--radius-round); color: var(--_text-color); display: inline-flex; inline-size: var(--size-6); justify-content: center; padding: 0; transform-style: preserve-3d; transition: background-color 0.1s ease, color 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
&:where([disabled]) { --_text-color: light-dark(rgb(0, 0, 0/0.3), rgb(255, 255, 255/0.26)); cursor: not-allowed; opacity: 0.64; }
svg { block-size: auto; inline-size: auto; max-block-size: var(--size-5); max-inline-size: var(--size-5); pointer-events: none; }
/* Ripple effect, utils.css */ &::before { --highlight-size: 130%; background-color: transparent;
@media (prefers-reduced-motion: no-preference) { transition: transform 0.2s ease, background-color 0.1s ease; } }
/* Colors — shared interaction model for critical and primary. Each color modifier below only swaps --_color; the tonal surface, contrast text, and hover/active strategy are identical. The .critical scope class in theme.css redirects --palette-source so --color-6 below becomes a tint of the active color. */ &.ui-critical, &.ui-primary { --_color-contrast: var(--gray-1); --_color-tonal: var(--color-6); --_accent-tonal-contrast: var(--text-primary-contrast);
--_default-hover-bg-color: oklch(from var(--_accent) l c h / 15%); --_default-active-bg-color: oklch(from var(--_accent) l c h / 25%);
--_filled-hover-bg-color: oklch(from var(--_accent) calc(l - 0.1) c h); --_filled-active-bg-color: oklch(from var(--_accent) calc(l - 0.15) c h);
--_outlined-active-bg-color: oklch(from var(--_accent) calc(l - 0.1) c h); }
&.ui-critical { --_color: var(--critical); }
&.ui-primary { --_color: var(--primary); }
/* Element states */ &:where(:not([disabled])) { &:where(:not(:active):hover) { &::before { background-color: var( --_interaction-hover-color, var(--_default-hover-bg-color) ); } }
&:where(:hover:active), &[aria-current="page"] { &::before { background-color: var( --_interaction-active-color, var(--_default-active-bg-color) ); } } }
/* Size */ &.ui-small { block-size: var(--size-4); inline-size: var(--size-4);
svg { max-block-size: var(--size-4); max-inline-size: var(--size-4); } }
/* Variants */ &.ui-outlined { --_bg-color: transparent; --_border-color: var(--_accent); --_text-color: var(--_accent); }
&.ui-tonal { --_bg-color: var(--_accent-tonal); --_text-color: var(--_accent-tonal-contrast); }
&.ui-filled { --_bg-color: var(--_accent); --_interaction-active-color: var(--_filled-active-bg-color); --_interaction-hover-color: var(--_filled-hover-bg-color); --_text-color: var(--_accent-contrast); } }}