Skip to content
Components, switch product

Button

<gui-button> is a button. It renders a native <button>, so it submits and resets forms like one, or a link styled as a button when you give it an href.

Code of the example above
ButtonBasic.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonBasic() {
return <GuiButton label="Save changes" />;
}

The element’s entry point is @golemui/gui-components/button, which registers <gui-button> and nothing else. To install the package and load the stylesheet, see Installation.

The @golemui/gui-components/react components have typed props and an on* prop for each event. React 18 or later.

ButtonExample.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonExample() {
return (
<GuiButton
label="Save changes"
/>
);
}

Listen for click on the element, as on any button. It has no events of its own.

Properties

Property Type Default Description
label string – Text of the button.
disabled boolean false Disables the button. A disabled link loses its href.
loading boolean false Shows a spinner and ignores clicks, while keeping the button focusable, for an action in progress.
icon string – Icon class name shown next to the text.
variant 'filled' | 'outlined' | 'link' 'filled' Visual style of the button: link looks like a link.
size 'sm' | 'md' | 'lg' 'md' Height of the button.
iconPosition attribute icon-position 'left' | 'right' 'left' Side of the text the icon is on.
type 'button' | 'submit' | 'reset' 'button' The native button type: submit submits the form it is in, and reset resets it.
name string – Name sent with the form submission when this button submits it.
value string – Value sent under name when this button submits the form.
form string – The id of the form the button belongs to, when it is outside it.
href string – Makes the button a link to this URL.
target string – Where the link opens, such as _blank.
rel string – The link's relationship to the page, such as noopener.
uid string – The id of the element's control. The ids of its label, hint and errors derive from it. Optional: when it is not set, the element generates one from its tag name and a page-wide counter, e.g. gui-textinput-3. Generated ids depend on creation order, so a server-rendered element must set uid or its ids will not match after hydration.

variant sets the style: filled, the default, for the main action, outlined for a secondary one, and link for a button that looks like a link.

Code of the example above
ButtonVariants.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonVariants() {
return (
<div className="gui-grid gui-grid--row gui-grid--justify-start">
<div className="gui-grid__cell">
<GuiButton label="Publish" />
</div>
<div className="gui-grid__cell">
<GuiButton variant="outlined" label="Preview" />
</div>
<div className="gui-grid__cell">
<GuiButton variant="link" label="Discard draft" />
</div>
</div>
);
}

size sets the height: sm, md, the default, or lg.

Code of the example above
ButtonSizes.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonSizes() {
return (
<div className="gui-grid gui-grid--row gui-grid--justify-start">
<div className="gui-grid__cell">
<GuiButton size="sm" label="Small" />
</div>
<div className="gui-grid__cell">
<GuiButton label="Medium" />
</div>
<div className="gui-grid__cell">
<GuiButton size="lg" label="Large" />
</div>
</div>
);
}

icon takes the class names of an icon, from any icon font or your own CSS, and shows it before the text. icon-position="right" puts it after. The icon is decorative and hidden from assistive technology. See Icons for the ways to draw it.

Code of the example above
ButtonIcon.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
// Any icon font works: icon sets these classes on the icon element.
// These draw Material Icons ligatures.
const iconStyles = `
.icon-download::before {
content: 'download';
font-family: 'Material Icons';
}
.icon-arrow::before {
content: 'arrow_forward';
font-family: 'Material Icons';
}
`;
export function ButtonIcon() {
return (
<>
<div className="gui-grid gui-grid--row gui-grid--justify-start">
<div className="gui-grid__cell">
<GuiButton icon="icon-download" label="Download invoice" />
</div>
<div className="gui-grid__cell">
<GuiButton
variant="outlined"
icon="icon-arrow"
iconPosition="right"
label="Next step"
/>
</div>
</div>
<style>{iconStyles}</style>
</>
);
}

loading shows a spinner in place of the icon and ignores clicks: your click listeners don’t run, so a second click can’t send the request twice. Unlike disabled, the button keeps its focus, and screen readers hear it is busy. Click the button to try it.

Code of the example above
ButtonLoading.tsx
import { useState } from 'react';
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonLoading() {
const [loading, setLoading] = useState(false);
const send = () => {
setLoading(true);
// Stands in for a request to your server.
setTimeout(() => setLoading(false), 2000);
};
return (
<GuiButton
id="send-invite"
label={loading ? 'Sending…' : 'Send invite'}
loading={loading}
onClick={send}
/>
);
}

A disabled button can’t be focused or clicked. Prefer to leave a button enabled and explain what is missing when it’s clicked: a disabled button gives no reason.

Code of the example above
ButtonDisabled.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonDisabled() {
return <GuiButton label="Delete project" disabled />;
}

With href, the element renders a link that looks like a button, for navigation rather than actions. target and rel go on the link. A disabled link loses its href, so it goes nowhere.

Code of the example above
ButtonLink.tsx
import { GuiButton } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonLink() {
return (
<div className="gui-grid gui-grid--row gui-grid--justify-start">
<div className="gui-grid__cell">
<GuiButton href="/components/docs/" label="Read the docs" />
</div>
<div className="gui-grid__cell">
<GuiButton
variant="outlined"
href="https://github.com/"
target="_blank"
rel="noopener"
label="Open GitHub"
/>
</div>
<div className="gui-grid__cell">
<GuiButton
variant="link"
href="/components/docs/"
label="Archived plans"
disabled
/>
</div>
</div>
);
}

The element renders a native <button> inside the form, so it works like one:

  • type="submit" submits the form. required and the other checks of the form’s fields run first. Enter in a field submits the form too, through its first submit button.
  • name and value are sent with the submission when that button submits it. The form’s submit event has the inner <button> as event.submitter: pass it to FormData to read them.
  • type="reset" resets the form: each field gets back the value it started with.
  • type="button", the default, does nothing in a form: handle its click yourself.

Pick a decision below: the form submits the comment and the button’s decision.

Code of the example above
ButtonNativeForm.tsx
import { useState, type SubmitEvent } from 'react';
import { GuiButton, GuiTextinput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
const initialComment = 'Looks good to me.';
export function ButtonNativeForm() {
const [comment, setComment] = useState<string | undefined>(initialComment);
const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
// event.submitter is the inner <button> that submitted the form.
const data = Object.fromEntries(
new FormData(event.currentTarget, event.submitter),
);
setResult(`Submitted ${JSON.stringify(data)}`);
};
return (
<form
id="review-form"
onSubmit={onSubmit}
onReset={() => setComment(initialComment)}
>
<div className="gui-grid gui-grid--row">
<div className="gui-grid__cell gui-grid__cell--span-2">
<GuiTextinput
label="Comment"
name="comment"
value={comment}
onGuiInput={(event) => setComment(event.detail.value)}
/>
</div>
<div className="gui-grid__cell">
<GuiButton
type="submit"
name="decision"
value="approve"
label="Approve"
/>
</div>
<div className="gui-grid__cell">
<GuiButton
type="submit"
name="decision"
value="decline"
variant="outlined"
label="Decline"
/>
</div>
<div className="gui-grid__cell">
<GuiButton type="reset" variant="link" label="Reset" />
</div>
</div>
<p id="review-result" role="status">
{result}
</p>
</form>
);
}

A button outside its form submits it with form, the id of the form, like a native button:

Code of the example above
ButtonOutsideForm.tsx
import { useState, type SubmitEvent } from 'react';
import { GuiButton, GuiTextinput } from '@golemui/gui-components/react';
import '@golemui/gui-components/index.css';
export function ButtonOutsideForm() {
const [result, setResult] = useState('');
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
setResult(`Submitted ${JSON.stringify(data)}`);
};
return (
<>
<form id="newsletter-signup" onSubmit={onSubmit}>
<GuiTextinput label="Email" name="email" required />
<p id="newsletter-signup-result" role="status">
{result}
</p>
</form>
<GuiButton type="submit" form="newsletter-signup" label="Subscribe" />
</>
);
}

How the elements submit, validate and reset is in Native forms.

<gui-button> renders into the light DOM, so you can style every part with plain CSS. These are the parts and their public classes:

gui-button
<gui-button class="gui-field">
<div class="gui-widget">
<button
type="submit"
id="gui-button-1"
class="gui-button-with-icon gui-button-icon-left gui-button--outlined"
>
<span
class="gui-widget-icon gui-button-icon icon-download"
data-icon="icon-download"
aria-hidden="true"
></span>
<span>Download invoice</span>
</button>
</div>
</gui-button>

With href, an <a> with the same classes takes the place of the <button>. While loading, the spinner takes the place of the icon:

gui-button loading
<button
type="button"
class="gui-button-with-icon gui-button-icon-left gui-button--loading"
aria-disabled="true"
aria-busy="true"
>
<span class="gui-button-icon gui-spinner"><svg>…</svg></span>
<span>Sending…</span>
</button>
ClassPart
gui-fieldThe element itself. Lines the button up with the fields of a gui-grid row.
gui-widgetThe box around the button.
gui-button-with-iconOn the button when it has an icon or a spinner.
gui-button-icon-left, gui-button-icon-rightOn the button: the side of the text the icon is on.
gui-button--outlined, gui-button--linkOn the button: the outlined and link variants.
gui-button--sm, gui-button--lgOn the button: the sm and lg sizes.
gui-button--loadingOn the button while loading.
gui-button-iconThe icon or the spinner.
gui-widget-iconThe icon, with your icon classes.
gui-spinnerThe spinner.

The stylesheet styles the native <button> and <a> inside gui-button, so select them as gui-button button and gui-button a.

The button reads these design tokens. Set them on :root to change every button, or on any element to change the buttons inside it:

TokenWhat it styles
--gui-intent-primaryThe background of filled, and the border and text of the others.
--gui-intent-primary-textThe text of filled.
--gui-intent-primary-hoverThe background of filled on hover.
--gui-intent-primary-activeThe background of filled while pressed, and the text of link.
--gui-radius-mdThe corner radius.
--gui-shadow-focusThe focus ring.
--gui-border-focusThe border of outlined while focused.
--gui-transition-normalThe speed of the background change on hover.
--gui-font-familyThe font.
--gui-font-smThe text size.

The heights are fixed, 32px, 42px and 50px for sm, md and lg: set height on gui-button button to change them.

styles.css
/* Pill-shaped buttons in a brand color, in one section only. */
.pricing gui-button {
--gui-radius-md: 999px;
--gui-intent-primary: #d96c3f;
--gui-intent-primary-hover: #c25a30;
--gui-intent-primary-active: #a84b26;
}

To restyle beyond the tokens, see Overriding styles; for dark mode and the Clay theme, see Themes and dark mode.

  • Name. The label names the button. Always set it: an icon-only button has no other way to get a name.
  • Loading. While loading, the button has aria-busy="true" and aria-disabled="true". It stays in the tab order and keeps focus, so the user isn’t lost when the action finishes.
  • Disabled. A disabled button is a native disabled <button>: it leaves the tab order. A disabled link has no href, keeps the link role and has aria-disabled="true".
  • Link or button. Use href for navigation and a button for actions, so screen readers announce what will happen.
KeyAction
TabMoves focus to the button, and away from it.
EnterActivates the button, or follows the link.
SpaceActivates the button, not a link.

What every element shares is in Accessibility.