Skip to content
NewService Map: 30 components for hosts, ports and routes
<PxButton />ReactActions & feedbackForm controlFree

Async button

Async button: a promise from onClick shows a pixel spinner, then a drawn check or a shake with "Try again", and returns to its label after 1.8 s.

View as Markdown
<PxButton />

Tryboth buttons here are wired to a fake 1.3-second task.

Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

Loading <PxButton />
Props
childrenlive
variantlive
loadinglive
disabledlive
React
<PxButton variant="default">Save changes</PxButton>

Installation

pnpm dlx shadcn@latest add @lumesec/px-button
First timeRegister the @lumesec registry once, or install by URL+

Adds @lumesec to your components.json:

pnpm dlx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json

Or skip the setup and install by URL:

npx shadcn@latest add https://elements.lumesec.ai/r/px-button.json

Usage

px-button-example.tsx
import { PxButton } from "@/components/lumesec/px-button";

const saveSettings = () => new Promise<void>((resolve) => setTimeout(resolve, 1300));

export function Example() {
  return <PxButton onClick={() => saveSettings()}>Save changes</PxButton>;
}

Behaviour

Return a promise from onClick (or pass one to run() on its ref) and it shows a pixel spinner, then a drawn check or a shake with “Try again”. Inside a form, type="submit" submits it.

API reference

Props

Also accepts every prop of <button> (React.ComponentProps<"button">), spread onto the root element.

childrenTypestringDefault"Save"

The label.

variantType"default" | "secondary" | "danger"Default"default"

default is the accent button, secondary a bordered surface button, danger uses the destructive colour.

loadingTypebooleanDefaultfalse

Shows the loading state while true, for loading driven from outside. Turning it on calls onStart; turning it off returns to the label.

loadingTextTypestringDefault"Working…"

Label while loading.

successTextTypestringDefault"Done"

Label after the task resolves.

errorTextTypestringDefault"Try again"

Label after the task rejects.

typeType"button" | "submit" | "reset"Default"button"

Native button type. submit submits its form, except while loading.

onClickType(event: React.MouseEvent<HTMLButtonElement>) => void | Promise<unknown>

Click handler. When it returns a promise, the button runs it like run(). Not called while loading.

onStartType() => void

Called when the loading state begins.

onSuccessType() => void

Called when the task resolves.

onErrorType(error: unknown) => void

Called with the rejection reason when the task fails.

Ref

ref receives a PxButtonHandle handle with these methods.

run<T>(task: () => Promise<T>): Promise<T>

Shows the loading state while task runs, then the success or error state for 1.8 s before returning to the label. Resolves with the task's result or rethrows its error. It does not check disabled.

focus(options?: FocusOptions): void

Moves focus to the button.

Accessibility

  • A native <button>; its accessible name is the visible label.
  • The label is an aria-live="polite" region, so the loading, success and error texts are announced.
  • aria-busy is true while loading.
  • The pixel icon canvas is aria-hidden.
  • A native button. With type="submit" a click submits its form, which runs validation; clicks while loading neither submit nor call onClick.
  • Reduced motion: the label swaps without rolling and the error shake is skipped; the pixel spinner and check still draw.

Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from --lumesec. See Theming.

This component reads --lumesec, --lumesec-foreground, --lumesec-success, --destructive, --card, --border and --foreground.

Notes

  • The label rolls between states. Pass the label as a string; it is not a slot for markup.
  • The root carries data-state set to loading, success or error outside the idle state, and data-variant.
  • ref is the handle, not the DOM button.

Was this page helpful?