# 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.
- React: `import { PxButton } from "@/components/lumesec/px-button"`
- Collection: Pixel UI (https://elements.lumesec.ai/components/pixel-ui)
- Registry item: https://elements.lumesec.ai/r/px-button.json
- Page: https://elements.lumesec.ai/components/pixel-ui/button



Live preview: https://elements.lumesec.ai/view/px-button

Demo source:

```tsx
"use client";

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

const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));

const failAfter = (ms: number) => sleep(ms).then(() => Promise.reject(new Error("Demo failure")));

export default function PxButtonDemo() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-4">
      <PxButton onClick={() => sleep(1300)}>Save changes</PxButton>
      <PxButton variant="secondary" onClick={() => failAfter(1300)}>
        Sync now
      </PxButton>
    </div>
  );
}
```

## Playground

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

## Installation

```bash
npx shadcn@latest add @lumesec/px-button
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/px-button.json
```

## Usage

React:

```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.

| Prop          | Type                                                                       | Default       | Description                                                                                                                              |
| ------------- | -------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `children`    | `string`                                                                   | `"Save"`      | The label.                                                                                                                               |
| `variant`     | `"default" \| "secondary" \| "danger"`                                     | `"default"`   | `default` is the accent button, `secondary` a bordered surface button, `danger` uses the destructive colour.                             |
| `loading`     | `boolean`                                                                  | `false`       | Shows the loading state while true, for loading driven from outside. Turning it on calls `onStart`; turning it off returns to the label. |
| `loadingText` | `string`                                                                   | `"Working…"`  | Label while loading.                                                                                                                     |
| `successText` | `string`                                                                   | `"Done"`      | Label after the task resolves.                                                                                                           |
| `errorText`   | `string`                                                                   | `"Try again"` | Label after the task rejects.                                                                                                            |
| `type`        | `"button" \| "submit" \| "reset"`                                          | `"button"`    | Native button type. `submit` submits its form, except while loading.                                                                     |
| `onClick`     | `(event: React.MouseEvent<HTMLButtonElement>) => void \| Promise<unknown>` | —             | Click handler. When it returns a promise, the button runs it like `run()`. Not called while loading.                                     |
| `onStart`     | `() => void`                                                               | —             | Called when the loading state begins.                                                                                                    |
| `onSuccess`   | `() => void`                                                               | —             | Called when the task resolves.                                                                                                           |
| `onError`     | `(error: unknown) => void`                                                 | —             | Called with the rejection reason when the task fails.                                                                                    |

### Ref

`ref` receives a `PxButtonHandle` handle with these methods.

| Method                                       | Description                                                                                                                                                                                              |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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](/docs/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.


