Repository navigation
Form buttons grow and their spinner sits high while they wait #636
Description
What happens
While a form button waits for its answer, it shows a spinner and a word like "Closing…" or "Saving…". On those buttons the spinner and the word sit above the middle of the button, and on smaller buttons the button also grows taller for as long as it waits. The answer usually comes quickly, so people see a jump: the button grows, its contents move up, then it all snaps back.
Measured in a local copy, with the request held open so the waiting state stays on screen:
- Close signup (12px text, in the ⋯ panel after Ask before closing a signup, and move Close signup into the ⋯ menu #571): 30px tall, then 34px while it says "Closing…". The spinner sits 2px above the middle. "Not yet" beside it stays 30px, so the two buttons no longer line up.
- Save on a signup's Settings tab (14px text): 36px, then 39px. The spinner sits 1.5px above the middle.
- Close signup in the phone's More actions sheet (15px text, at least 48px tall): the height holds, but the spinner and the word sit about 2px high.
Where: AsyncSubmitButton in src/components/ui/async-submit-button.tsx. It wraps the spinner and the word in an inline-flex span. That span's first item is the spinner, an empty inline-block with no text, so the browser takes the span's baseline from the spinner's bottom edge. The span then sits on the text's baseline with all of its 16px above it, which lifts it, and the descent below the baseline adds to the line's height.
Every AsyncSubmitButton does this, since none of them makes the button itself a flex box:
- Publish and Close signup in the signup header and the phone's More actions sheet (Ask before closing a signup, and move Close signup into the ⋯ menu #571 and One Publish and share button, in a bar on phones; drafts show no public link #578 replace these with the ⋯ panel's Close signup question and Publish and share);
- Save and "Yes, delete signup" on a signup's Settings tab;
- "Yes, remove" on the Responses tab;
- Create signup on the new signup page;
- Disconnect on Connected apps;
- Allow and Don't allow on the consent screen.
Publish and share's "Checking…" from #580 doesn't do it, because its spinner sits in a block-level grid.
Fix, in outline
- In
AsyncSubmitButton, make the wrapper a block-level flex box:flex items-center justify-center gap-2instead ofinline-flex items-center gap-2.justify-centerkeeps the contents centred in buttons wider than their text, such as the consent screen's full-width ones. - Tried in place in the browser: the Close signup and Save buttons then keep their height while waiting, and the spinner sits exactly in the middle.
Watch for
- Every user of the component changes at once. Look at each one while it waits, on a computer and at phone width. They're listed above. A tophat can hold the request open (for example, delay any POST carrying a
Next-Actionheader in Playwright'spage.route) so the waiting state stays on screen. - Left-aligned buttons. If any user relies on the contents sitting at the start of a wide button,
justify-centermoves them. None does today. - The word still changes the button's width. "Closing…" is a little wider than "Close signup", so "Not yet" moves left by about 4px. That's separate. Publish and share avoids it by keeping both labels in one grid cell, if that's wanted here too.
Done when
- None of the buttons listed above changes height while it waits, and the spinner sits in the middle, checked in the running app on a computer and at 390px wide.
pnpm lint && pnpm typecheck && pnpm knip && pnpm testpass.
Found while tophatting #571 on 2026-10-25, after the owner saw the Close signup button jump. Checked by measuring the waiting buttons in a local copy and trying the fix in the browser. The component is the same on main at 35288fa.
Generated by Claude Code
Activity
Merge main into claude/issue-636-async-button-centre