Button
ui-button wraps a native <button> in its shadow root and adds visual variants and sizes. It is form-associated.
With type="submit" or type="reset" it submits or resets the form it sits in, even though the inner button lives
in a shadow root. It also forwards the native invoker attributes popovertarget, commandfor and command, so it
can open popovers and dialogs without script.
Unlike a native <button>, the default type is button, so a ui-button inside a form does nothing until you
set type="submit" or type="reset".
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button>Button</ui-button>
</body>
</html>Examples
Variants
Set variant to default, destructive, outline, secondary, ghost or link.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button>Default</ui-button>
<ui-button variant="secondary">Secondary</ui-button>
<ui-button variant="destructive">Destructive</ui-button>
<ui-button variant="outline">Outline</ui-button>
<ui-button variant="ghost">Ghost</ui-button>
<ui-button variant="link">Link</ui-button>
</body>
</html>Sizes
Text buttons come in xs, sm, default and lg.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button size="xs">Extra small</ui-button>
<ui-button size="sm">Small</ui-button>
<ui-button>Default</ui-button>
<ui-button size="lg">Large</ui-button>
</body>
</html>Icon buttons
The icon, icon-xs, icon-sm and icon-lg sizes make a square button. A slotted <svg> is sized to the button
text. Give icon-only buttons an accessible name with aria-label.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button variant="outline" size="icon-xs" aria-label="Add">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
</ui-button>
<ui-button variant="outline" size="icon-sm" aria-label="Add">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
</ui-button>
<ui-button variant="outline" size="icon" aria-label="Add">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
</ui-button>
<ui-button variant="outline" size="icon-lg" aria-label="Add">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
</ui-button>
</body>
</html>With an icon
Put an <svg> before or after the label. The button adds a gap between them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 12v8h16v-8M12 3v12M7 8l5-5 5 5" /></svg>
Upload
</ui-button>
<ui-button variant="outline">
Next
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
</ui-button>
</body>
</html>Loading
Combine disabled with a ui-spinner while work is in progress.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/spinner.html"></import-element>
<ui-button disabled><ui-spinner size="14"></ui-spinner> Saving</ui-button>
</body>
</html>Disabled
disabled disables the inner button and reflects to the host, so ui-button[disabled] can be styled from outside.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button disabled>Disabled</ui-button>
<ui-button variant="outline" disabled>Disabled</ui-button>
</body>
</html>In a form
type="submit" submits the surrounding form and type="reset" resets it. Buttons inside a disabled <fieldset> are
disabled too.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
<import-element from="./button-form-demo.html"></import-element>
<button-form-demo></button-form-demo>
</body>
</html><custom-element name="button-form-demo">
<script>
const result = useState({ text: "Submit the form to see its data." });
function submit(event) {
event.preventDefault();
const entries = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`);
result.text = entries.join("\n") || "(no fields)";
}
function reset() {
result.text = "The form was reset.";
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 4);
width: 100%;
max-width: calc(var(--spacing) * 80);
}
form {
display: grid;
gap: calc(var(--spacing) * 3);
}
.actions {
display: flex;
gap: calc(var(--spacing) * 2);
}
output {
display: block;
padding: calc(var(--spacing) * 3);
border-radius: var(--radius-md);
background: var(--muted);
font-family: var(--font-mono);
font-size: var(--text-xs);
white-space: pre-wrap;
}
</style>
<form onsubmit="submit(event)" onreset="reset()">
<ui-input name="title" value="Quarterly report" aria-label="Title"></ui-input>
<div class="actions">
<ui-button type="submit">Save</ui-button>
<ui-button type="reset" variant="outline">Reset</ui-button>
</div>
</form>
<output><slot name="#result.text"></slot></output>
</custom-element>Open a popover
popovertarget and popovertargetaction work as on a native button. The target is looked up by id in the same
document or shadow root.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button variant="outline" popovertarget="button-popover">Toggle popover</ui-button>
<div id="button-popover" popover>Opened from a ui-button.</div>
</body>
</html>Invoke a command
commandfor and command are the native invoker commands. Browsers without native support get the same
behaviour. The button dispatches a cancelable command event on the target and then calls showModal(), close(),
requestClose(), togglePopover(), showPopover() or hidePopover(). When the target is a component such as
ui-dialog rather than a native <dialog>, show-modal, close and request-close are sent as --show-modal,
--close and --request-close, because browsers only deliver built-in dialog commands to native dialogs.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Button demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<ui-button commandfor="button-dialog" command="show-modal">Open dialog</ui-button>
<dialog id="button-dialog">
<p>This dialog was opened with commandfor.</p>
<ui-button variant="outline" commandfor="button-dialog" command="close">Close</ui-button>
</dialog>
</body>
</html>API
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
variant |
"default" | "destructive" | "outline" | "secondary" | "ghost" | "link" |
"default" |
Visual style. |
size |
"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" |
"default" |
Height and padding. The icon sizes make a square button. |
type |
"button" | "submit" | "reset" |
"button" |
What the button does in its form. |
disabled |
boolean | false |
Disables the button. Reflects to the attribute. Also true while the button is inside a disabled <fieldset>. |
aria-label |
string | Accessible name, passed to the inner button. Use it for icon-only buttons. Property name: label. |
|
aria-description |
string | Accessible description, passed to the inner button. ui-tooltip sets it from its content. Property name: description. |
|
popovertarget |
string | Id of a popover element to toggle. | |
popovertargetaction |
"toggle" | "show" | "hide" |
Action taken on the popover target. | |
commandfor |
string | Id of the element that receives command. |
|
command |
string | Command to invoke, such as show-modal, close, request-close, toggle-popover, show-popover, hide-popover or a custom --command. |
Events
ui-button dispatches no events of its own. Listen for click as on any element. When native invoker commands are
not supported, the button dispatches a cancelable command event on the commandfor target with command and
source properties. Calling preventDefault() stops the built-in action.
Slots
| Slot | Description |
|---|---|
| (default) | The label and any icons. Slotted <svg> elements are sized to calc(var(--spacing) * 4). |
CSS parts
| Part | Description |
|---|---|
button |
The inner native <button>. |
CSS states and custom properties
| Name | Description |
|---|---|
--ui-radius-start |
Overrides the start corner radii. ui-button-group sets it to 0 to join buttons. |
--ui-radius-end |
Overrides the end corner radii. |
:state(attr-variant-is-*), :state(attr-size-is-*) |
Set for the current variant and size, for example ui-button:state(attr-variant-is-outline). |
Forms
ui-button is form-associated through ElementInternals, so it finds its form even across shadow roots.
type="submit" calls requestSubmit() on the form, which runs constraint validation and fires submit.
type="reset" calls reset(). The button itself adds no name or value to the submitted data, and the submit
event's submitter is null.
Accessibility
The host delegates focus to the inner native <button>, so Enter and Space
activate it as usual. aria-label is passed to the inner button. A disabled button is removed from the tab order.