Skip to content

Button

  • Submitting a form, saving or deleting
  • Opening a Dialog, a Menu or a Popover
  • Any action that changes something on this page

Not to go to another page — use a Link, so it can be opened in a new tab and is announced as a link.

Choosing between actions and navigation:

ComponentUse it forFor example
ButtonDoes something on this page: saves, deletes, opens a dialog“Save”, “Delete”, “Add to cart”
LinkGoes somewhere: another page, a section, a file“View invoice”, “Read the docs”
ToggleTurns a state on or off and stays pressed“Bold”, “Mute”

Choosing between rows of controls:

ComponentUse it forFor example
ToolbarSeveral controls acting on one thing, reached with one Tab stop and the arrow keysAn editor’s formatting bar
ToggleGroupA set of Toggles sharing one valueText alignment, bold / italic / underline
Buttons in a rowA few independent actions, each its own Tab stopCancel / Save at the end of a form
import { Button } from '@arun-dev/ui';
export default function ButtonVariants() {
return (
<>
<Button>Ghost</Button>
<Button variant="primary">Primary</Button>
<Button variant="danger">Delete</Button>
<Button variant="danger-ghost">Delete</Button>
<Button disabled>Disabled</Button>
</>
);
}

Choosing a variant:

VariantUse it forFor example
primaryThe one main action of a form or dialog“Save”, “Create account”
ghostEvery other action“Cancel”, “Edit”
dangerThe last step of deleting, which cannot be undone“Delete” in the confirmation Dialog
danger-ghostStarting a deletion, before a confirmation asks“Delete” in a form footer or on a row

While a save is in flight, set pending. The button is disabled — a second press, or Enter in a field, cannot submit again — and a Spinner turns over the label, which keeps its place so the button does not change width. Clear it when the request settles, whether it succeeded or failed.

import { useState } from 'react';
import { Button } from '@arun-dev/ui';
// A save that takes a moment, as an API call does: pending from the submit until it settles.
export default function ButtonPending() {
const [pending, setPending] = useState(false);
return (
<form
onSubmit={(event) => {
event.preventDefault();
setPending(true);
setTimeout(() => setPending(false), 2000);
}}
>
<Button type="submit" variant="primary" pending={pending}>
Save changes
</Button>
</form>
);
}

With react-hook-form, pass formState.isSubmitting: <Button type="submit" pending={isSubmitting}>.

<Button variant="ghost">Click me</Button>
Button props
PropTypeDefaultDescription
variant'ghost' | 'primary' | 'danger' | 'danger-ghost''ghost'Visual weight. primary is the filled call to action; danger and danger-ghost delete or discard.
type'button' | 'submit' | 'reset''button'Defaults to button so it never submits a form by accident.
disabledbooleanfalsePrevents activation, using the platform rather than synthesising it.
pendingbooleanfalseBusy with the action it started. Disabled while it lasts, with a Spinner over the label.
classNamestringConcatenated with the component's own classes, never replacing them.
renderReactElementElement or component to render instead of the default. Props, className, event handlers and ref are merged onto it.
refRef<HTMLElement>Ref to the rendered element. Merged with any ref on the render element.
…restHTMLAttributesAnything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM.
  • A bare Button renders <button type="button"> — the explicit type means it never submits a form by accident.
  • type is not forwarded to a component supplied through render — pass it on the element there.
  • Button does not navigate. A link is a separate Link component.
  • pending disables the button natively, so focus leaves it and assistive technology skips it while it lasts. Announce the outcome — a Toast saying “Saved”, or the error — rather than the wait.