React Bulk Action Bar
A batch operation applied to the wrong selection is one of the few genuinely unrecoverable mistakes a data tool lets you make. The undo window is the entire design.
| Lot | Product | Status | |
|---|---|---|---|
| L2411 | KLV-7000 | WIP | |
| L2412 | KLV-5000 | Hold | |
| L2413 | KLV-3000 | Released | |
| L2414 | KLV-7000 | WIP | |
| L2415 | KLV-5000 | Hold | |
| L2416 | KLV-3000 | Released | |
| L2417 | KLV-7000 | WIP | |
| L2418 | KLV-5000 | Hold |
3 selected
Select rows to raise the bar. “Scrap” is destructive and irreversible — it asks for confirmation and offers no undo.
Installation
Props
| Prop | Type | Description |
|---|---|---|
| selectedCount | number | Drives visibility — hidden at zero |
| totalCount | number | Enables "select all matching" |
| actions | BulkAction[] | { id, label, icon?, destructive?, irreversible? } |
| undoSeconds | number | Undo window length (default 8) |
| onAction | (actionId, count) => void | Fires after any confirmation |
| onUndo | (actionId) => void | Fires within the undo window |
| contained | boolean | Pin inside a positioned ancestor |
Undo, unless you cannot
destructive and irreversible are separate flags, and the distinction is the point.
A destructive action gets a two-step confirmation — the button changes to "Confirm — scrap 3" so the count is restated at the moment of commitment, which catches the case where the selection changed since you last looked.
An irreversible action additionally gets no undo affordance. Showing an Undo button for something that cannot be undone is worse than showing nothing: it invites a wrong assumption at exactly the moment the user is most anxious. Those actions carry a warning icon in the bar instead, and after they run the bar simply clears.
Everything else lands in an undo strip with a visible countdown. Eight seconds is short enough that your backend can hold a tombstone rather than a full transaction, and long enough for the "wait, that was the wrong filter" reflex.
contained pins the bar inside a positioned ancestor rather than the viewport, which is how the demo keeps it inside its own panel — and note the demo reserves space below the table so the bar never covers the final row.
New components every week
Get the week's new Kelvin UI components and templates in one short email. No spam, unsubscribe anytime.