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.
Request access · Fieldline workspace
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| requirements | { id: string; label: string; met: boolean }[] | 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> | — | Sends the form. Return a promise and the dots bounce until it settles; the signal aborts on Escape. |
| state | "idle" | "pending" | "success" | "error" | — | Controlled action state. Every move goes through onStateChange and waits for this. |
| onStateChange | (state: "idle" | "pending" | "success" | "error") => void | — | Each action state, from the press, the answer or the timer that caused it. |
| onLockedPress | (missing: { id: string; label: string; met: boolean }[]) => void | — | A press while locked, with what is still missing: scroll to the first field, flag it. |
| segments | "arc" | "ring" | "pips" | "arc" | How the padlock wears the requirements: an arc open at the foot, a closed ring, or one pip each. |
| warm | number | 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" | "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 | "Still needed" | The list's heading, also used in the spoken description. |
| hintSide | "top" | "bottom" | "top" | Which side of the button the hint opens on. |
| hintAlign | "start" | "center" | "end" | "end" | Which edge of the button the hint lines up with; end suits a form footer's right-hand button. |
| label | string | "Submit request" | The button's text. |
| pendingLabel | string | "Sending" | The text while sending. |
| successLabel | string | "Sent" | The text once sent. |
| errorLabel | string | "Retry" | The text after a failed send; a press tries again. |
| accent | string | "var(--accent)" | The met segments and the warm fill. Any CSS colour. |
| successHold | number | 1600 | How long Sent holds before the button rests, in ms. |
| errorHold | number | 2600 | How long the error holds before the button rests, in ms. |
| size | "sm" | "md" | "lg" | "md" | 32, 40 or 48 px tall; the padlock scales with it. |
| sound | boolean | 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 | false | Dims and disables the button; no hint opens. |
| className | string | — | Merged onto the root's classes. |