Skip to content

Status Button

A full-width submit button that shows idle, sending, sent and failed.

$npx shadcn@latest add https://framebrick.co/r/status-button.json

From the Ciketo template, see it in context.

Press it: it succeeds, then fails, then succeeds again.

Installation

$ npx shadcn@latest add https://framebrick.co/r/status-button.json

Usage

import { StatusButton } from "@/components/ui/status-button";

const [state, action, pending] = useActionState(send, { status: "idle" });

<form action={action}>
  …fields…
  <StatusButton status={pending ? "pending" : state.status} labels={{ idle: "Request a quote" }} />
</form>

Props

PropTypeDefaultDescription
status"idle" | "pending" | "success" | "error""idle"What the button shows. The form owns it; the button only displays it.
labelsPartial<Record<status, string>>—Replace any of the four texts: "Send message", "Sending", "Thank you", "Something went wrong — try again".
typestring"submit"Button type.
…propsbutton props—Passed to the button (className sets the width — it is full width by default).
  • The result stays until the form is sent again, as in Framer: the button follows `status` and never resets it itself.
  • From the submit buttons of Kalme, Titen and Ciketo, which share one design. It is not `subscribe-input`: that one is an e-mail field with a round button, this is a button for any form.
  • The colours change over 0.2 s on cubic-bezier(0.44, 0, 0.56, 1). Failed is a red label on a 15% red fill.
  • Hover fades the fill to 95% and press mixes it 20% toward the foreground colour. Framer fades to 85% and 70%, which lightens the fill under the label: in a themed project with a mid-tone primary that is 3.9:1 on hover and 3.0:1 pressed (the Blocks "soft" sample theme, light). At 95% the label keeps 4.7:1 or better in all four sample themes, light and dark; the press keeps 6.6:1 or better because it moves away from the label (darker in a light theme, lighter in a dark one).
  • In light mode the red label is the destructive colour darkened by a quarter (6.6:1): destructive on its own 15% tint is only 3.65:1. Dark mode uses the destructive colour as it is (5.8:1).
  • The spinner keeps turning with reduced motion on purpose — it is the only sign that something is happening.
  • The result is announced from a polite live region next to the button, not inside it: a button's children are presentational, so a live region there may never be read. While sending it is disabled, aria-busy, and named by its sending label.
  • Plain CSS: no animation library.

AI prompt

Paste into v0, Lovable, Bolt or Claude to rebuild this component in your own project.

Create a React + Tailwind CSS component called StatusButton — plain CSS, no animation library.
- A button, type submit by default, h-10 w-full rounded-md px-4 text-sm font-medium, inline-flex centred, with a visible
  focus ring (ring-[3px] ring-ring/50). Prop status: idle | pending | success | error (default idle) and a labels prop to
  replace the four texts ("Send message", "Sending", "Thank you", "Something went wrong — try again").
- Colours change with transition-colors, 200ms, cubic-bezier(0.44,0,0.56,1). idle, pending and success: bg-primary
  text-primary-foreground; idle hovers to bg-primary/95 and presses to bg-[color-mix(in_oklab,var(--primary),var(--foreground)_20%)]
  (darker in a light theme, lighter in a dark one — a fade toward the page would put the label under 4.5:1 in a themed
  project with a mid-tone primary). error: bg-destructive/15 with the
  label in text-[color-mix(in_oklab,var(--destructive),black_25%)] and dark:text-destructive — the plain destructive
  colour on its own 15% tint is only 3.65:1 in light mode.
- pending: the button is disabled with aria-busy, shows a 20px spinner (a conic-gradient from transparent to currentColor
  with a radial-gradient mask that leaves a 2px ring, animate-spin) and an sr-only "Sending". success: a small check icon
  then the label. error: the label.
- Render a polite live region (role status, sr-only) NEXT TO the button, not inside it — a button's children are
  presentational — that holds the success or error text and is empty otherwise.
- The form owns the state: the button never resets itself, so the result stays until the form is sent again.

Be the first to know

Get new templates and components first

One short email when we release a template or a free component. No spam — unsubscribe anytime.

We use your email only for Framebrick updates. Privacy policy