Components
Icon Button
Icon buttons are used to trigger actions and are typically found in toolbars, cards, and dialogs.
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="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="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="icon-button 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="icon-button 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="icon-button 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="icon-button outlined 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="icon-button tonal 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="icon-button filled 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="icon-button filled 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="icon-button filled 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 .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="icon-button 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="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="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="icon-button 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="icon-button 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="icon-button 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 .sr-only util). |
Have a visible label that you reference with aria-labelledby | Not used. |
Anatomy
- Container:
<button class="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(.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 .1s ease, color .1s ease, border-color .1s ease, box-shadow .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 .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. */ &.critical, &.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); }
&.critical { --_color: var(--critical); }
&.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 */ &.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 */ &.outlined { --_bg-color: transparent; --_border-color: var(--_accent); --_text-color: var(--_accent); }
&.tonal { --_bg-color: var(--_accent-tonal); --_text-color: var(--_accent-tonal-contrast); }
&.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); } }}