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.

LotProductStatus
L2411KLV-7000WIP
L2412KLV-5000Hold
L2413KLV-3000Released
L2414KLV-7000WIP
L2415KLV-5000Hold
L2416KLV-3000Released
L2417KLV-7000WIP
L2418KLV-5000Hold

3 selected

Select rows to raise the bar. “Scrap” is destructive and irreversible — it asks for confirmation and offers no undo.

Installation

npx shadcn@latest add "https://kelvinui.com/registry/bulk-action-bar.json"

Props

PropTypeDescription
selectedCountnumberDrives visibility — hidden at zero
totalCountnumberEnables "select all matching"
actionsBulkAction[]{ id, label, icon?, destructive?, irreversible? }
undoSecondsnumberUndo window length (default 8)
onAction(actionId, count) => voidFires after any confirmation
onUndo(actionId) => voidFires within the undo window
containedbooleanPin 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.