KQ-1717 · NUMBER/MATCH
Number Match
A new-device approval drawn as the two screens it happens on: the browser signing in, showing a two-digit number, and the trusted phone asking whether it's you with three numbers to choose from. The right choice joins the screens — a line grows from the number and from the phone's frame level with the chosen key, each half on glide, and they meet in the middle where a dot lands on recoil — while both screens turn to success and a check stamps on recoil; a wrong one reaches in danger, stops short and draws back on a tween as both screens explain the block. The choices are buttons with roving focus (arrows, Home, End; Enter or Space chooses), outcomes are announced, a lifetime bar drains only while it can be seen, and the screens sit side by side, overlap or mirror, stacking in a narrow container.
Check your phone
Tap 47 on your phone to finish signing in.
rae@fieldline.dev
Are you trying to sign in?
- Desktop browser on a laptop
- Near Porto, Portugal
- 14:32 · rae@fieldline.dev
Tap the number shown on your other screen.
waiting · tap 47 · Fieldline sign-in
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/number-match
Props
- request
- { number, choices?, device, place, time, account }
- default defaultNumberMatchRequest
- The sign-in being approved: the number shown, the phone's choices (seeded when omitted), and what, where and when.
- status / defaultStatus
- "waiting" | "approved" | "blocked" | "denied" | "expired"
- default — / "waiting"
- Where the request stands, controlled or initial.
- onStatusChange
- (status) => void
- default —
- Fires from the choice, the denial or the expiry that changed it.
- onApprove
- (request) => void
- default —
- The right number was chosen.
- onDeny
- (request, reason: "mismatch" | "denied") => void
- default —
- The request was stopped: a wrong number, or "No, it's not me".
- onExpire
- (request) => void
- default —
- The request ran out of time before anyone chose.
- onRetry
- () => void
- default —
- Try again or Send a new request on the signing-in screen; pass a new request to start over.
- expiresIn
- number
- default 60000
- How long the request lives, in ms.
- timeZone
- string
- default "UTC"
- The time zone the request's time is shown in.
- appName
- string
- default "Fieldline"
- The product's name, on both screens.
- signInUrl
- string
- default "fieldline.app/sign-in"
- The address in the signing-in window's bar.
- secureHref
- string
- default "#security"
- Where Change your password goes after a block or a denial.
- layout
- "split" | "overlap" | "mirror"
- default "split"
- The two screens side by side, the phone over the window's edge, or the phone first; they stack in narrow containers.
- numberSize
- number
- default 56
- The number's size on the signing-in screen, in px; the tile's padding scales with it.
- accent
- string
- default "var(--accent-bright)"
- Any CSS colour for the number, the waiting highlights, the lifetime bar and the app marks.
- disabled
- boolean
- default false
- The choices and the denial are unavailable.
- className
- string
- default —
- Classes for the outer wrapper.
| Prop | Type | Default | Description |
|---|---|---|---|
| request | { number, choices?, device, place, time, account } | defaultNumberMatchRequest | The sign-in being approved: the number shown, the phone's choices (seeded when omitted), and what, where and when. |
| status / defaultStatus | "waiting" | "approved" | "blocked" | "denied" | "expired" | — / "waiting" | Where the request stands, controlled or initial. |
| onStatusChange | (status) => void | — | Fires from the choice, the denial or the expiry that changed it. |
| onApprove | (request) => void | — | The right number was chosen. |
| onDeny | (request, reason: "mismatch" | "denied") => void | — | The request was stopped: a wrong number, or "No, it's not me". |
| onExpire | (request) => void | — | The request ran out of time before anyone chose. |
| onRetry | () => void | — | Try again or Send a new request on the signing-in screen; pass a new request to start over. |
| expiresIn | number | 60000 | How long the request lives, in ms. |
| timeZone | string | "UTC" | The time zone the request's time is shown in. |
| appName | string | "Fieldline" | The product's name, on both screens. |
| signInUrl | string | "fieldline.app/sign-in" | The address in the signing-in window's bar. |
| secureHref | string | "#security" | Where Change your password goes after a block or a denial. |
| layout | "split" | "overlap" | "mirror" | "split" | The two screens side by side, the phone over the window's edge, or the phone first; they stack in narrow containers. |
| numberSize | number | 56 | The number's size on the signing-in screen, in px; the tile's padding scales with it. |
| accent | string | "var(--accent-bright)" | Any CSS colour for the number, the waiting highlights, the lifetime bar and the app marks. |
| disabled | boolean | false | The choices and the denial are unavailable. |
| className | string | — | Classes for the outer wrapper. |