Skip to content
ShineUI
Install

Button

Every action, verb first.

<sui-button>ButtonsStable

About

The action button. Six roles cover every click: accent is the one primary action per view, brand submits forms, outline is the default, ghost is quiet, danger confirms a destructive step and link looks like a link. Labels are verb first, sentence case. Clicks are the native click event.

Usage

<sui-button variant="accent" label="Pay bills"></sui-button> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiButton } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiButton variant="accent" label="Pay bills" />; }
<script setup> import { SuiButton } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiButton variant="accent" label="Pay bills" /> </template>
import { Component } from "@angular/core"; import { SuiButton } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiButton], template: `<sui-button variant="accent" label="Pay bills"></sui-button>`, }) export class Example { }

Variants

One accent button per view. brand submits forms, outline is the default, ghost is quiet, danger confirms a destructive step, and link is a button dressed as a link; give it href and it is a real one.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="accent" label="Pay bills"></sui-button> <sui-button variant="brand" label="Sign in"></sui-button> <sui-button variant="outline" label="Export"></sui-button> <sui-button variant="ghost" label="Cancel"></sui-button> <sui-button variant="danger" label="Void invoice"></sui-button> <sui-button variant="link" label="Forgot password?"></sui-button> </div>

Icons

icon-pos="end" puts the icon after the label, and it swaps sides with the text under dir="rtl". An icon with no label has no accessible name: give it aria-label, or reach for sui-icon-button.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="accent" icon="fa-solid fa-plus" label="New invoice"></sui-button> <sui-button variant="outline" icon="fa-regular fa-paper-plane" icon-pos="end" label="Send"></sui-button> <sui-button variant="ghost" icon="fa-solid fa-print" aria-label="Print"></sui-button> </div>

Sizes

sm is 28px, md is 34px and lg is 42px, the same control heights the input family uses, so a button lines up in a row with a field.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="brand" size="sm" label="Small"></sui-button> <sui-button variant="brand" label="Medium"></sui-button> <sui-button variant="brand" size="lg" label="Large"></sui-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 and a modal trap around it holds; disabled takes it out of the tab order.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="brand" loading label="Saving"></sui-button> <sui-button variant="accent" disabled label="Pay bills"></sui-button> <sui-button variant="outline" disabled label="Export"></sui-button> </div>

Pill and block

pill rounds the ends. block makes the host a block and fills it, for a sign-in card.

<div style="display:flex;flex-direction:column;gap:12px;max-width:320px;align-items:flex-start"> <sui-button variant="outline" pill label="Pill"></sui-button> <sui-button variant="brand" block label="Sign in"></sui-button> </div>

With a badge

The default slot renders after the label, so a sui-badge can count what the action opens.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="outline" icon="fa-regular fa-envelope" label="Inbox"><sui-badge value="4"></sui-badge></sui-button> <sui-button variant="ghost" label="Drafts"><sui-badge value="12" severity="soft"></sui-badge></sui-button> </div>

In a form

A brand button is the role for a submit, but it still needs type="submit". name and value say which button sent the form.

Not submitted.
<form id="btn-form" style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="brand" type="submit" name="action" value="save" label="Save"></sui-button> <sui-button variant="outline" type="submit" name="action" value="send" label="Save and send"></sui-button> <sui-button variant="ghost" type="reset" label="Reset"></sui-button> <span id="btn-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('#btn-form-log'); const form = root.querySelector('#btn-form'); form.addEventListener('submit', (e) => { e.preventDefault(); const b = e.submitter; log.textContent = b ? `Submitted with ${b.name}=${b.value}.` : 'Submitted.'; }); form.addEventListener('reset', () => { log.textContent = 'Reset.'; }); }

href renders the button as a real link in the same clothes, so middle-click, open in new tab and the URL in the status bar 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-button variant="accent" href="#/invoices/new" icon="fa-solid fa-plus" label="New invoice"></sui-button> <sui-button variant="outline" href="https://example.com/help" target="_blank" icon="fa-solid fa-arrow-up-right-from-square" icon-pos="end" label="Help center"></sui-button> <sui-button variant="link" href="#/account/password" label="Forgot password?"></sui-button> <sui-button variant="outline" href="#/exports" disabled label="Export"></sui-button> </div>

Destructive actions

Inline, an outline, ghost or link button takes tone="danger": danger ink and no fill, so the accent stays the one solid orange in view. The filled danger variant is the confirmation step, in a dialog footer where it is the only fill.

<div style="display:flex;flex-wrap:wrap;gap:10px;align-items:center"> <sui-button variant="outline" tone="danger" icon="fa-regular fa-trash-can" label="Void invoice"></sui-button> <sui-button variant="ghost" tone="danger" label="Remove"></sui-button> <sui-button variant="link" tone="danger" label="Delete account"></sui-button> <span style="width:1px;height:24px;background:var(--border)"></span> <sui-button variant="danger" label="Void invoice"></sui-button> <sui-button variant="danger" loading label="Voiding"></sui-button> </div>

Keyboard

KeyAction
TabLands on the button. A disabled button is skipped; a loading one keeps its place in the tab order, aria-disabled rather than disabled, so a keyboard user does not lose focus while it settles. A link with href is a tab stop as any link is; a disabled link has no href and is skipped.
Enter SpaceNative. A type="submit" button submits the surrounding form and a type="reset" button resets it; the default type="button" fires click and nothing else. With href, Enter follows the link and Space scrolls the page, as on any link.

API

<sui-button>

Properties

PropertyAttributeTypeDefaultDescription
blockblockbooleanfalseFull width: the host becomes a block and the button fills it.
disableddisabledbooleanfalseBlocks clicks. Filled roles take the disabled well, an outline keeps its surface, and ghost and link stay bare; on navy (tone="inverse") the ink fades instead. 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 button as a real link, <a class="sui-btn"> in the same variant, size, glyph and label, 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-paper-plane". An icon with no label and no slot content has no accessible name: give the element an aria-label, which is forwarded to the button, or reach for sui-icon-button. An aria-label should contain the visible label.
iconPosicon-pos"end" | "start"'start'Where the icon sits in reading order: start puts it before the label and end after it, so it swaps sides under dir="rtl". Any other value reads as start.
labellabelstringstring | undefinedButton text. Prefer this over the slot.
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. This element is not form-associated: the inner <button> is the surrounding form's submitter, so name and value post one entry, and only when this button is the one that submitted. Inert on a link (href).
pillpillbooleanfalsePill radius instead of the 8px control radius.
relrelstringstring | undefinedLink relationship, written as given. Unset, a _blank target gets noopener. Nothing without href.
sizesize"lg" | "md" | "sm"'md'Control height: sm 28px, md 34px (toolbars), lg 42px (forms). Reflected.
targettargetstringstring | undefinedWhere the link opens, e.g. _blank. Nothing without href.
tonetone"danger" | "inverse" | undefinedRe-inks the quiet roles, outline, ghost and link. Filled roles ignore it. danger is destructive ink for a button that deletes, removes or voids, so a row or toolbar carries a destructive action without a second solid fill beside the accent; the filled danger variant is the confirmation step's. inverse is for a quiet button sitting on dark chrome, an app bar or a side rail, where the default ink would read against the panel rather than on it. It paints --ink-inverse-muted and brightens to --ink-inverse on hover, matching sui-icon-button's tone of the same name so a labelled button and an icon button agree on a brand bar.
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" | "link" | "outline"'outline'Visual role, not behavior: a brand button still needs type="submit". accent = the one primary action per view; brand = form submit; outline = default; ghost = quiet; danger = the filled confirmation of a destructive action (the dialog footer), not the row action. link is a button dressed as a link; give it href and it is a real one, so middle-click, open in new tab and the URL in the status bar work. Any role takes href.

Methods

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

Slots

SlotDescription
(default)Extra content, rendered after label and sharing the same 7px gap. label is not replaced by slotted content; both render.

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