KQ-1268 · GATE/BUTTON

Gate Button

A submit button that shows its own readiness: a padlock at its left wears a segmented arc, one segment per requirement, and each requirement the form meets is drawn along the arc on the flick spring. When the last is met the shackle pops open, lifting on the recoil spring and swinging about its long leg on snap, and the button warms as an accent bloom grows out of the padlock across the face on glide; pressed, the padlock gives way to three dots bouncing in a wave while the request runs, then the dots run together and a check draws on flick as the label rolls to Sent. Locked, it stays focusable: hover or keyboard focus lists what is missing, Enter or Space is refused with a rattle and the first missing item flagged, and Escape closes the list or cancels a send.

KQ-1268 · GATE/BUTTON

Request access · Fieldline workspace

Locked. Still needed: Team chosen, Data policy accepted.

locked · 1 of 3 ready

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/gate-button

Props

requirements
{ id: string; label: string; met: boolean }[]
default defaultRequirements
What the form needs before it can be sent; one arc segment each. With none, the button is ready.
onSubmit
(signal: AbortSignal) => void | Promise<unknown>
default —
Sends the form. Return a promise and the dots bounce until it settles; the signal aborts on Escape.
state
"idle" | "pending" | "success" | "error"
default —
Controlled action state. Every move goes through onStateChange and waits for this.
onStateChange
(state: "idle" | "pending" | "success" | "error") => void
default —
Each action state, from the press, the answer or the timer that caused it.
onLockedPress
(missing: { id: string; label: string; met: boolean }[]) => void
default —
A press while locked, with what is still missing: scroll to the first field, flag it.
segments
"arc" | "ring" | "pips"
default "arc"
How the padlock wears the requirements: an arc open at the foot, a closed ring, or one pip each.
warm
number
default 0.7
How strongly the button warms once it is ready, 0 to 1: an accent outline at 0, a solid accent fill with light text at 1.
hint
"list" | "next" | "off"
default "list"
What hovering or focusing the locked button shows: every requirement ticked or not, only the next one missing, or nothing (the description still names them).
hintTitle
string
default "Still needed"
The list's heading, also used in the spoken description.
hintSide
"top" | "bottom"
default "top"
Which side of the button the hint opens on.
hintAlign
"start" | "center" | "end"
default "end"
Which edge of the button the hint lines up with; end suits a form footer's right-hand button.
label
string
default "Submit request"
The button's text.
pendingLabel
string
default "Sending"
The text while sending.
successLabel
string
default "Sent"
The text once sent.
errorLabel
string
default "Retry"
The text after a failed send; a press tries again.
accent
string
default "var(--accent)"
The met segments and the warm fill. Any CSS colour.
successHold
number
default 1600
How long Sent holds before the button rests, in ms.
errorHold
number
default 2600
How long the error holds before the button rests, in ms.
size
"sm" | "md" | "lg"
default "md"
32, 40 or 48 px tall; the padlock scales with it.
sound
boolean
default false
Play a click for each requirement the visitor meets, the shackle's snap, a dull click on a refused press and a chime when sent. Requirements met by the host alone are silent. No audio context exists unless this is on.
disabled
boolean
default false
Dims and disables the button; no hint opens.
className
string
default —
Merged onto the root's classes.