Skip to content
ShineUI
Install

IconButton

One icon, one action, one required label.

<sui-icon-button>ButtonsStable

About

Icon-only button for toolbars, table rows and card headers. The element's own aria-label is required: it names the control and doubles as the tooltip. Roles: ghost is the quiet default, outline stands alone on a surface, brand is navy, accent is the one primary action per view and danger is destructive. Clicks are the native click event.

Usage

<sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit"></sui-icon-button> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiIconButton } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiIconButton icon="fa-regular fa-pen-to-square" aria-label="Edit" />; }
<script setup> import { SuiIconButton } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiIconButton icon="fa-regular fa-pen-to-square" aria-label="Edit" /> </template>
import { Component } from "@angular/core"; import { SuiIconButton } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiIconButton], template: `<sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit"></sui-icon-button>`, }) export class Example { }

Variants

The element's own aria-label is the accessible name and the tooltip; there is no ariaLabel prop. Without one the button renders unnamed and gets no tooltip.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit"></sui-icon-button> <sui-icon-button icon="fa-regular fa-copy" aria-label="Duplicate" variant="outline"></sui-icon-button> <sui-icon-button icon="fa-regular fa-bell" aria-label="Alerts" variant="brand"></sui-icon-button> <sui-icon-button icon="fa-solid fa-plus" aria-label="Add" variant="accent"></sui-icon-button> <sui-icon-button icon="fa-regular fa-trash-can" aria-label="Delete" variant="danger"></sui-icon-button> </div>

Sizes and shape

sm, md and lg match the control heights. The button takes the control radius every button takes, so it sits square beside fields and selects. pill gives the round look, for the places a round control is the convention, such as an arrow over a photo.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit" variant="outline" size="sm"></sui-icon-button> <sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit" variant="outline"></sui-icon-button> <sui-icon-button icon="fa-regular fa-pen-to-square" aria-label="Edit" variant="outline" size="lg"></sui-icon-button> <sui-icon-button icon="fa-solid fa-chevron-right" aria-label="Next photo" variant="outline" pill></sui-icon-button> </div>

Out of the tab order

no-tab-stop sets tabindex="-1" on the button, for a control the keyboard already reaches another way: the overflow arrows on sui-tabs duplicate what the arrow keys do inside the bar. Tab into the row below and only the first button takes focus. Never set it on a control with no other keyboard route.

Nothing focused.
<div id="ib-skip" style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-solid fa-chevron-left" aria-label="Show earlier tabs"></sui-icon-button> <sui-icon-button icon="fa-solid fa-chevron-right" aria-label="Show later tabs" no-tab-stop></sui-icon-button> <span id="ib-skip-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Nothing focused.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#ib-skip-log'); root.querySelector('#ib-skip').addEventListener('focusin', (e) => { const btn = e.target.closest('sui-icon-button'); log.textContent = btn ? `Focused ${btn.getAttribute('aria-label')}.` : 'Nothing focused.'; }); }

Destructive actions

tone="danger" on ghost or outline is the trash on a row: danger ink, no fill. The filled danger variant is the confirmation step.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-regular fa-trash-can" aria-label="Delete invoice" tone="danger"></sui-icon-button> <sui-icon-button icon="fa-regular fa-trash-can" aria-label="Delete invoice" variant="outline" tone="danger"></sui-icon-button> <sui-icon-button icon="fa-regular fa-trash-can" aria-label="Delete invoice" variant="danger"></sui-icon-button> </div>

On navy chrome

tone="inverse" gives ghost and outline inverse ink and border for a navy header or footer. Brand, accent and danger ignore it.

<div style="display:flex;gap:10px;align-items:center;padding:12px 16px;border-radius:10px;background:var(--brand)"> <sui-icon-button icon="fa-solid fa-magnifying-glass" aria-label="Search" tone="inverse"></sui-icon-button> <sui-icon-button icon="fa-regular fa-bell" aria-label="Alerts" tone="inverse"></sui-icon-button> <sui-icon-button icon="fa-solid fa-gear" aria-label="Settings" variant="outline" tone="inverse"></sui-icon-button> </div>

Without the tooltip

An icon button draws a tooltip from its aria-label. no-tooltip leaves it out when the name is already on screen beside the button, as a row that repeats the word; the name stays on the button for a reader.

Duplicate this invoice
<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <span style="font:460 13px/1.6 var(--font-sans);color:var(--ink)">Duplicate this invoice</span> <sui-icon-button icon="fa-regular fa-copy" aria-label="Duplicate this invoice" no-tooltip></sui-icon-button> </div>

Loading and disabled

Loading swaps the icon for a spinner and marks the button aria-disabled, so a keyboard user keeps focus until it settles; disabled takes it out of the tab order.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-solid fa-rotate" aria-label="Sync" loading></sui-icon-button> <sui-icon-button icon="fa-solid fa-rotate" aria-label="Sync" variant="brand" loading></sui-icon-button> <sui-icon-button icon="fa-regular fa-copy" aria-label="Duplicate" disabled></sui-icon-button> <sui-icon-button icon="fa-regular fa-copy" aria-label="Duplicate" variant="outline" disabled></sui-icon-button> </div>

An avatar as the glyph

Slot the glyph when it is not a Font Awesome class. A sm avatar inside a sm pill button is the account trigger: the ghost hover paints a one-pixel ring around the disc, never a box around it, and the focus ring sits on the same circle.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button aria-label="Account menu" size="sm" pill><sui-avatar name="Dana Whitfield" size="sm" accent></sui-avatar></sui-icon-button> <sui-icon-button aria-label="Account menu" pill><sui-avatar name="Priya Raman" size="md"></sui-avatar></sui-icon-button> <sui-icon-button aria-label="Account menu" size="lg" pill><sui-avatar name="Tom Okafor" size="lg"></sui-avatar></sui-icon-button> </div>

In a form

type, name and value work as they do on any submit button. The label still comes from aria-label.

Not submitted.
<form id="ib-form" style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-solid fa-print" aria-label="Print" variant="outline" type="submit" name="action" value="print"></sui-icon-button> <span id="ib-form-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Not submitted.</span> </form>
// setup(root): properties and events setup(root) { const log = root.querySelector('#ib-form-log'); root.querySelector('#ib-form').addEventListener('submit', (e) => { e.preventDefault(); const b = e.submitter; log.textContent = b ? `Submitted with ${b.name}=${b.value}.` : 'Submitted.'; }); }

href renders the control as a real link with the same glyph, name and tooltip, so middle-click and open in new tab work; target="_blank" adds rel="noopener". type, name, value and form do nothing on a link, and a disabled link drops its href and reads aria-disabled.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-icon-button icon="fa-regular fa-file-lines" aria-label="Open invoice" href="#/invoices/1043"></sui-icon-button> <sui-icon-button icon="fa-solid fa-arrow-up-right-from-square" aria-label="Help center" variant="outline" href="https://example.com/help" target="_blank"></sui-icon-button> <sui-icon-button icon="fa-regular fa-file-lines" aria-label="Open invoice" href="#/invoices/1043" disabled></sui-icon-button> </div>

Keyboard

KeyAction
TabLands on the button unless no-tab-stop is set, which is for a control the keyboard already reaches another way. A disabled button is skipped; a loading one keeps its place, aria-disabled rather than disabled. A link with href is a tab stop as any link is; a disabled link has no href and is skipped.
Enter SpaceNative. The same three types as sui-button: submit, reset and the default button. With href, Enter follows the link and Space scrolls the page, as on any link.

API

<sui-icon-button>

Properties

PropertyAttributeTypeDefaultDescription
disableddisabledbooleanfalseBlocks clicks. As on sui-button, filled roles take the disabled well, an outline keeps its surface, a ghost stays bare, and on navy the ink fades. A disabled link keeps the look, drops its href, reads aria-disabled="true" and takes no pointer events, since an anchor has no disabled attribute of its own.
formformstringstring | undefinedAssociates the button with a form by id, for a submitter that sits outside the form it submits: a dialog footer, or a sticky action bar. Inert on a link (href).
hrefhrefstringstring | undefinedRenders the control as a real link, <a class="sui-ib"> with the same variant, size, glyph, name and tooltip, so middle-click, open in new tab, "Copy link address" and the URL in the status bar all work. type, name, value and form do nothing on a link, since an anchor takes no part in a form; disabled keeps the look, drops the href and reads aria-disabled; loading is the same contract as on the button. Clicks are still the native click.
iconiconstringstring | undefinedFont Awesome class, e.g. "fa-regular fa-pen-to-square". Leave it unset and slot the glyph instead. The accessible name comes from the element's own aria-label attribute, which is also the sui-tooltip text; there is no ariaLabel prop. Without one the button renders unnamed and gets no tooltip. An action that needs a visible label is sui-button.
loadingloadingbooleanfalseShows a spinner in place of the icon and blocks clicks. The button stays focusable while busy, aria-disabled rather than disabled, so a keyboard user keeps their place and a modal trap around it holds; a click, Enter or Space does nothing until it clears. A link behaves the same and keeps its href. Reflected.
namenamestringstring | undefinedForm control name, forwarded to the native button. Inert on a link (href).
noTabStopno-tab-stopbooleanfalseTakes the button out of the tab order (tabindex="-1"), for a control the keyboard already reaches another way: the overflow arrows on a tab bar, which duplicate what the arrow keys do inside the bar, are the case this exists for. The button stays clickable and still takes focus from a pointer or from setFocus(). Do not set it on a control with no keyboard route of its own, which is how a control becomes reachable by mouse alone.
noTooltipno-tooltipbooleanfalseLeaves out the tooltip the aria-label would otherwise draw, for a button whose name is already on screen beside it: a chip's remove control, a row that repeats the word. The name stays on the button for a reader.
pillpillbooleanfalseRound. Off by default: the button takes the control radius every other button takes, so a toolbar of buttons, selects and icon buttons reads as one row of shapes. Set it where a round control is the convention, an arrow over a photo say.
relrelstringstring | undefinedLink relationship, written as given. Unset, a _blank target gets noopener. Nothing without href.
sizesize"lg" | "md" | "sm"'md'Square size: sm 28px, md 34px (toolbars), lg 42px. Reflected.
targettargetstringstring | undefinedWhere the link opens, e.g. _blank. Nothing without href.
tonetone"danger" | "inverse" | undefinedInk for the quiet roles (ghost and outline; brand, accent and danger ignore it). inverse is inverse ink and border for a button on the brand chrome. danger is the inline destructive action, the trash on a row: danger ink with no fill, so the row does not gain a second solid beside the accent; the filled danger variant stays the confirmation step's. Unset keeps the canvas/surface recipe.
typetype"button" | "reset" | "submit"'button'Native button type. submit participates in the surrounding form; reset clears it. Inert on a link (href): an anchor takes no part in a form.
valuevaluestringstring | undefinedValue submitted with the form when this button submits it. Inert on a link (href).
variantvariant"accent" | "brand" | "danger" | "ghost" | "outline"'ghost'Visual role: ghost (default, quiet), outline (bordered), brand (the brand fill), accent (the accent fill, the one primary action), danger (destructive).

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the button, or to the link when href is set.

Slots

SlotDescription
(default)The glyph, when it is not a Font Awesome class: a sui-avatar for an account menu, an inline SVG mark. Each avatar size is two pixels smaller than the control size of the same name, so size="sm" around a sm avatar paints the ghost hover as a one-pixel ring around the disc rather than a box around it, which is how GitHub and Vercel draw the trigger. The spinner replaces slotted content while loading, exactly as it replaces icon.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also