KQ-2080 · TWO/KEYS

Two Keys

A two-person approval for an agent's action, drawn as two key switches beneath a summary of it: each approver turns their own key a quarter turn on springs.snap, the first key opens a window that drains as a hairline, and only when the second turns inside it do the two halves of the bar between the switches close on springs.glide, the lock swap open on springs.flick and the Run control unlock — while a lone key turns itself back on snap if the window lapses. Only the current user's key is a control: a real switch that Space or Enter turns, or a sideways drag that follows the pointer 1:1 and commits by projection; under 34rem the switches stack beside a shorter summary with the bar running down between them.

KQ-2080 · TWO/KEYS

Gaugeworks billing · approvals · you are Mara Ilves

Gaugeworks billing agent · 4 min ago

Refund £4,820.00

to Basinworks Supply Co.

Over the £2,500 single-approver limit
Mara Ilves (you)Finance leadYour key
Theo BrandtSupport managerWaitingTheo Brandt, Support manager: key not turned.

Needs both keys within 60 s: finance lead and support manager.

Needs both keys turned within 60 seconds.

waiting for both keys · 60 s window · gaugeworks

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/two-keys

Props

switchStyle
"key" | "lever" | "dial"
default "key"
A key in a round keyhole plate, a throw lever swinging across an arc, or a dial whose ring fills as it turns.
windowLength
number
default 60
Seconds the second key has after the first one turns.
accent
string
default "var(--accent-bright)"
Lit ticks, the key, the closed bar and Run — any CSS colour.
action
TwoKeysAction
default defaultTwoKeysAction
What the agent wants to do: title, amount, currency, payee, reference, reason, limitNote, requestedBy, requestedAt and expiresAt.
approvers
TwoKeysApprover[]
default defaultTwoKeysApprovers
The two people whose keys it needs, in switch order: id, name, role, turnedAt, rejected ({ at, note }) and comment.
currentUser
string
default "mara"
Whose key is a control here; every other key is a drawing with its state in text. Someone who is not an approver is offered Withdraw.
turned / defaultTurned
boolean
default — / false
Whether the current user's key is turned, controlled or initial.
onTurnKey
(turned: boolean, reason: "key" | "lapsed") => void
default —
Fires from the press, the drag, Space or Enter — or with "lapsed" when the window ran out on the user's own key.
onReject
(note: string) => void
default —
Reject, with the reason the approver wrote (a reason is required).
onRemind
(approverId: string) => void
default —
Remind, for an approver whose key is not turned.
onWithdraw
() => void
default —
Withdraw, offered to anyone viewing who is not an approver.
onRun
() => void
default —
The unlocked Run control was pressed.
now
number
default defaultTwoKeysNow
The moment times count from, in ms; while a window is open the card keeps time from it itself.
status
"open" | "expired" | "withdrawn" | "done"
default "open"
Whether the request can still be decided; done means it ran. It also reads as expired once now passes action.expiresAt.
runLabel
string
default "Run refund"
The Run control's words.
format
(amount: number, currency: string) => string
default en-GB currency
Formats the amount.
label
string
default the action's title and amount
The card's accessible name.
disabled
boolean
default false
Nothing can be turned or pressed.
className
string
default —
Classes for the outer surface.