KQ-1781 · SAFETY/NUMBERS
Safety Numbers
An end-to-end verification screen that lays both safety numbers side by side as grids of five-digit groups: pointing at or focusing a group lights its partner and draws a hairline between them, and the compare-aloud stepper walks the pairs one at a time with an outline that travels cell to cell on springs.snap. Marking the chat verified turns each matched pair over in 3D on springs.glide, both grids together and staggered in reading order by cascade(), to a calm tick, and once the last one lands a seal settles on the contact on springs.snap with one ring breathing out; a changed number shows its warning first. Each grid is a role="grid" walked with the arrows, Home, End and Ctrl+Home/End, and every group's name spells its digits so a screen reader reads them one by one.
Verify Rafi Okafor
@rafi · Waylight
not verified · 12 groups
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/safety-numbers
Props
- yourCode
- string
- default defaultYourCode
- The safety number this device computed: digits, any spacing (60 digits make 12 groups).
- theirCode
- string
- default defaultTheirCode
- The number on the contact's device, as read or scanned. Groups that differ are struck through and verifying is held back.
- contact
- SafetyContact
- default defaultSafetyContact
- Who is being verified: { name, handle?, device? }.
- youName
- string
- default "Your phone"
- How this side is named over its grid.
- verified / defaultVerified
- boolean
- default — / false
- The verified state, controlled or initial. A change plays the flip and the seal, whoever made it.
- onVerify
- () => void
- default —
- "Mark as verified" was pressed and every group matches.
- onReset
- () => void
- default —
- "Clear verification" was pressed; the ticks turn back in reverse order.
- onScan
- () => void
- default —
- "Scan code" was pressed: open your camera flow, then set verified.
- onCompare
- (index, result) => void
- default —
- A group was compared aloud: its index and "same" or "different".
- changed / previousCode / onAcknowledgeChange
- boolean / string / () => void
- default false
- The contact's number changed: a warning shows first, and once acknowledged the groups that differ from previousCode carry a dot.
- verifiedAt / now
- number
- default — / defaultSafetyNow
- When the chat was verified and the moment relative times count from, in ms since the epoch.
- groupSize
- number
- default 5
- Digits per group.
- columns
- number
- default 3
- Groups per row in each grid.
- groupStyle
- "cells" | "chips" | "digits"
- default "cells"
- Groups as hairline cells, filled pill chips, or one small box per digit.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Digit size, cell height, grid gaps and card padding.
- accent
- string
- default "var(--accent-bright)"
- Compare outline, connector, ticks and seal — any CSS colour; fills take its hue at a fixed lightness.
- renderCode
- (code) => ReactNode
- default a seeded picture of the digits
- Draws your number as a scannable code in the card's QR view; pass your real encoder here.
- status / onRetry
- "ready" | "loading" | "error" / () => void
- default "ready"
- Loading shows skeleton grids; error shows a message and Try again, which calls onRetry.
- height
- number
- default 540
- The surface's height in px; the body scrolls inside it.
- disabled
- boolean
- default false
- Read only: the numbers show and can be explored, nothing can be marked.
- className
- string
- default —
- Classes for the outer section.
| Prop | Type | Default | Description |
|---|---|---|---|
| yourCode | string | defaultYourCode | The safety number this device computed: digits, any spacing (60 digits make 12 groups). |
| theirCode | string | defaultTheirCode | The number on the contact's device, as read or scanned. Groups that differ are struck through and verifying is held back. |
| contact | SafetyContact | defaultSafetyContact | Who is being verified: { name, handle?, device? }. |
| youName | string | "Your phone" | How this side is named over its grid. |
| verified / defaultVerified | boolean | — / false | The verified state, controlled or initial. A change plays the flip and the seal, whoever made it. |
| onVerify | () => void | — | "Mark as verified" was pressed and every group matches. |
| onReset | () => void | — | "Clear verification" was pressed; the ticks turn back in reverse order. |
| onScan | () => void | — | "Scan code" was pressed: open your camera flow, then set verified. |
| onCompare | (index, result) => void | — | A group was compared aloud: its index and "same" or "different". |
| changed / previousCode / onAcknowledgeChange | boolean / string / () => void | false | The contact's number changed: a warning shows first, and once acknowledged the groups that differ from previousCode carry a dot. |
| verifiedAt / now | number | — / defaultSafetyNow | When the chat was verified and the moment relative times count from, in ms since the epoch. |
| groupSize | number | 5 | Digits per group. |
| columns | number | 3 | Groups per row in each grid. |
| groupStyle | "cells" | "chips" | "digits" | "cells" | Groups as hairline cells, filled pill chips, or one small box per digit. |
| density | "compact" | "regular" | "roomy" | "regular" | Digit size, cell height, grid gaps and card padding. |
| accent | string | "var(--accent-bright)" | Compare outline, connector, ticks and seal — any CSS colour; fills take its hue at a fixed lightness. |
| renderCode | (code) => ReactNode | a seeded picture of the digits | Draws your number as a scannable code in the card's QR view; pass your real encoder here. |
| status / onRetry | "ready" | "loading" | "error" / () => void | "ready" | Loading shows skeleton grids; error shows a message and Try again, which calls onRetry. |
| height | number | 540 | The surface's height in px; the body scrolls inside it. |
| disabled | boolean | false | Read only: the numbers show and can be explored, nothing can be marked. |
| className | string | — | Classes for the outer section. |