{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loading-button",
  "title": "Loading Button",
  "description": "Submit button that shows a spinner, announces itself and refuses to fire twice while an async action is in flight. Set loading={true} for the duration of the request — form submit, save, sign-in, checkout, delete confirmation, \"generate\" in an AI app, or any handler that awaits fetch — and the button disables itself so a second click cannot send the request again; loadingText swaps the label for \"Saving…\" or \"Charging card…\" while it waits. Common asks it answers: \"react button with loading spinner\", \"disable button while submitting\", \"prevent double submit\", \"async submit button\", \"pending state button shadcn\", \"button spinner while awaiting fetch\", \"form submit loading state\". Official shadcn/ui has no loading state anywhere in this path: its button ships no loading, pending or busy prop, and its spinner is a bare spinning icon with an aria-label — pairing them, disabling the button, and keeping the two in step is left to you, and doing it by hand is where the double-submit bug comes from. This one carries aria-busy while pending, and the spinner it composes is pulld's, which puts the label in a polite live region rather than only on the icon, so the wait is announced instead of being a silent frozen button. Two things worth knowing before you drop it in a form: it defaults to type=\"button\", so pass type=\"submit\" explicitly when it submits a form, and disabling a focused button takes it out of the tab order — the live region is what carries the state to a screen reader once focus has moved. The spinner atom installs with it through registryDependencies; there is nothing else to add.",
  "dependencies": [],
  "registryDependencies": [
    "https://pulld.pages.dev/r/spinner.json"
  ],
  "files": [
    {
      "path": "registry/ui/loading-button.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Spinner } from \"@/registry/ui/spinner\"\n\ninterface LoadingButtonProps extends React.ComponentPropsWithoutRef<\"button\"> {\n  /** When true, shows a spinner and disables the button. */\n  loading?: boolean\n  /** Optional label shown in place of children while loading. */\n  loadingText?: string\n}\n\nexport function LoadingButton({\n  loading = false,\n  loadingText,\n  children,\n  disabled,\n  className,\n  ...props\n}: LoadingButtonProps) {\n  return (\n    <button\n      type=\"button\"\n      disabled={disabled || loading}\n      aria-busy={loading}\n      className={cn(\n        \"inline-flex h-9 items-center justify-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      {loading ? <Spinner className=\"text-current\" label={loadingText ?? \"Loading\"} /> : null}\n      {loading && loadingText ? loadingText : children}\n    </button>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
