Field History

Flags a field someone else just changed and shows its previous value with a one-tap revert, so a teammate's change isn't overwritten unknowingly.

StopsOverwrote teammate's change unknowingly

Installation

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

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

What it catches

  • Recent changes by someone else (within recentFor)
  • The previous value and who changed it
  • Reverts that would also replace your unsaved edit
  • Regret after a revert, with Undo

Props

FieldHistory props
PropTypeDefault
label* (required)

The field's label.

string—
htmlFor

The id of the field's input, so the label is a real `<label>`.

string—
children* (required)

The field itself (an input, select...).

React.ReactNode—
history* (required)

Saved values, newest first. The first one is what's saved now.

FieldVersion[]—
value

What the field holds right now, if it may differ from the saved value (an unsaved edit).

string—
onRevert* (required)

Puts one value back. Return a promise to show a pending state; a rejection keeps things as they were.

(value: string) => void | Promise<void>—
format

Formats a value for display, e.g. `(v) => "₹" + v`.

(value: string) => string(v) => v
you

The current user's name as it appears in `by`, so your own edits don't get flagged.

string—
recentFor

A change by someone else is flagged on the trigger for this long, in ms.

number3 * DAY
defaultOpen

Open the history panel on first render.

booleanfalse
limit

How many older values the list shows.

number4
wasText

Templates: `{previous}`, `{by}` and `{when}` are filled in.

string"Was {previous} until {by} changed it {when}."
changedText

Trigger text while a recent change is flagged. `{when}` is filled in.

string"Changed {when}"
historyLabel

Accessible label for the history trigger.

string"History"
revertLabel

Text for the revert button.

string"Revert to {value}"
restoreLabel

Text for restoring an older value.

string"Restore"
restoreAriaLabel

Screen reader name of each Restore button. `{value}` is filled in. Defaults to `restoreLabel` plus the value.

string`${restoreLabel} {value}`
replacesText

Shown above revert when the field holds an unsaved edit. `{value}` is filled in.

string"Replaces your unsaved {value}."
revertedText

Shown after a revert. `{value}` is filled in.

string"Reverted to {value}."
undoLabel

Text for the Undo button.

string"Undo"
emptyText

Shown when there is no history.

string"empty"
locale

Language for "when" ("vor 2 Tagen"). Unset keeps the built-in English wording.

string—
formatWhen

Full control over "when", e.g. your app's own relative-time helper. Wins over `locale`.

(at: Date | number | string, now: number) => string—

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

Accessibility

  • Pass htmlFor so the label is a real <label> for the field
  • The trigger has aria-expanded, aria-controls and an aria-label that says when it changed
  • Animations respect prefers-reduced-motion

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

Limitations

  • History comes from your props; it doesn't track changes itself
  • Reverting runs your onRevert

What it stops

Overwrote teammate's change unknowingly

Also known as

field history · audit log · change history · revert · who changed · activity · collaboration · price change

Field type

Dependencies

Pairs well with