Bulk Scope Confirm

States exactly how many records a bulk action will hit and defaults to this page only, so "select all" doesn't hit every record.

StopsBulk action hit every record

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/bulk-scope-confirm.json

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

What it catches

  • "All matching" scope when only this page was meant (page is the default)
  • Risky subsets inside the selection, counted by category
  • The match count changing while the dialog is open, which locks Confirm until acknowledged

Props

BulkScopeConfirm props
PropTypeDefault
action* (required)

Verb for the action, e.g. "Archive".

string—
page* (required)

Only the items selected on the current page.

ScopeDetails—
all* (required)

Every item matching the current filter, across all pages.

ScopeDetails—
noun

Singular and plural noun, e.g. ["file", "files"].

[string, string]["item", "items"]
scope

Controlled scope.

BulkScope—
defaultScope

Initial scope when uncontrolled. Defaults to the safer "page".

BulkScope"page"
onScopeChange

Called when the user switches between this page and all matching.

(scope: BulkScope) => void—
onConfirm* (required)

Called with the scope the user confirmed. Return a promise to show a pending state; a rejection is caught.

(scope: BulkScope) => void | Promise<void>—
onCancel

Called when the user cancels.

() => void—
variant

Visual style of the confirm button.

keyof typeof variants"default"
pageLabel

Label for the this-page option.

string"Selected on this page"
allLabel

Label for the all-matching option.

string"All matching"
pendingLabel

Confirm button content while onConfirm runs.

React.ReactNode"Working…"
cancelLabel

Cancel button content.

React.ReactNode"Cancel"
acknowledgeLabel

Button that accepts a changed count and unlocks Confirm.

React.ReactNode"Use new count"
driftMessage

Warning shown when the "all" count changes while this is open.

(from: string, to: string) => React.ReactNode(from, to) => `The match changed from ${from} to ${to} wh…
locale

BCP 47 locale for numbers.

string—

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

Accessibility

  • Scope choice is a fieldset with a legend and labelled radio options
  • The summary and the drift warning are aria-live="polite"
  • Confirm uses aria-disabled while locked, aria-busy while pending and aria-describedby to the drift warning
  • Breakdown and sample lists have aria-labels

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: Resolve the scope on the server at execution time, check the count you confirmed, and authorise each record.

  • Counts and categories come from your props; it does not query the data
  • Drift is only noticed when you pass updated counts while it is open

What it stops

Bulk action hit every record

Also known as

bulk delete · select all · bulk edit · mass action · data table · admin panel · batch update

Field type

Dependencies

Pairs well with