Edit Lock

Locks a finished or approved record and states that edits need re-approval before unlocking, so approved invoices aren't changed by mistake.

StopsApproved invoice edited by mistake

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/edit-lock.json

The files land in your project. They're yours to edit.

Usage

edit-lock-usage.tsx
import { EditLock } from "@/components/ui/edit-lock"

<EditLock lockedBy="Priya" relockAfter={30_000} onEdit={() => setStatus("Needs re-approval")}>
  <div className="grid grid-cols-2 gap-3 text-sm">
    <label className="col-span-2 space-y-1.5">
      <span className="font-medium">Vendor</span>
      <input className={field} defaultValue="Shree Packaging Pvt Ltd" />
    </label>
    <label className="space-y-1.5">
      <span className="font-medium">Amount (₹)</span>
      <input className={field} inputMode="decimal" defaultValue="1,84,500" />
    </label>
    <label className="space-y-1.5">
      <span className="font-medium">Due</span>
      <input className={field} defaultValue="30 Oct 2026" />
    </label>
  </div>
</EditLock>

What it catches

  • Accidental edits to approved records
  • Unlocking without knowing the consequence, via a confirmation step
  • Approvals voided by an unlock with no real change (only real edits void them)
  • Records left unlocked, by relocking after idleness

Props

EditLock props
PropTypeDefault
lockedBy* (required)

Who locked it, e.g. "Priya".

string—
reason

Why it is locked, e.g. "approval", "sign-off".

string"approval"
approvalId

Identifies the approval. A new value (a fresh approval) clears the "needs re-approval" state.

string—
locked

Controlled lock state.

boolean—
defaultLocked

Initial lock state when uncontrolled.

booleantrue
onLockedChange

Called when the record locks or unlocks.

(locked: boolean) => void—
onEdit

Called once, on the first change made after unlocking: the moment the approval stops being valid.

() => void—
relockAfter

Relocks after this many ms without any typing, clicking or key presses inside.

number60_000
lockedText

Templates: `{name}`, `{reason}` and `{time}` are filled in.

string"Locked by {name} after {reason}"
unlockLabel

Text for the unlock button.

string"Unlock to edit"
consequenceText

Explains what unlocking means (e.g. re-approval).

string"Any change voids {name}'s {reason}. It will need re-appr…
confirmUnlockLabel

Text for confirming the unlock.

string"Unlock"
cancelLabel

Text for cancelling the unlock.

string"Cancel"
unlockedText

Shown while the record is unlocked.

string"Unlocked · nothing changed yet · relocks in {time}"
editedText

Shown once a change has voided the approval.

string"Edited · needs re-approval · relocks in {time}"
staleText

Shown when the record changed elsewhere.

string"Locked · changed since {name}'s {reason}, needs re-appro…
lockNowLabel

Text for the lock-now button.

string"Lock now"
unlockedAnnouncement

Read to screen readers instead of the ticking visible text, which would be announced every second.

string"Unlocked. Nothing changed yet."
editedAnnouncement

Announced to screen readers when an edit voids the approval.

string"Edited. Needs re-approval."
relockSoonText

Announced once when the idle relock is this close: `{time}` is filled in.

string"Relocks in {time} unless you keep working."
relockWarnWithin

How close the relock must be, in ms, before `relockSoonText` is announced.

number10_000

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Locks with a native disabled <fieldset>
  • Lock state and edits are announced in an aria-live="polite" region
  • The unlock confirmation focuses its confirm button; Escape cancels and returns focus
  • After unlocking, focus moves to the first field

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Enforce the lock on the server: reject edits to approved records unless the approval is voided and logged.

  • A UI lock: the record can still be changed through your API
  • Re-approval is signalled through onEdit; your workflow must act on it

What it stops

Approved invoice edited by mistake

Also known as

read only · locked form · approval · invoice · purchase order · fieldset disabled · edit mode · po

Field type

Dependencies

Pairs well with