Bulk action bar
A toolbar for the selected items: count, select all and clear, actions with single key shortcuts, a More menu for the rest, loading per action, and confirmation for destructive ones.
npx prodular-ui add bulk-action-barInvoices
Three actions as buttons, the rest under More. Press R, A, E or D while the bar is open; Escape clears. Delete confirms first; Archive is disabled with a reason.
- INV-2104Northwind Logistics$5,900
- INV-2103Kestrel Health$8,420
- INV-2102Halcyon Robotics$11,227
- INV-2101Meridian Freight$622
- INV-2100Lumen Analytics$3,591
2 invoices selected
"use client"
import * as React from "react"import { ArchiveIcon, DownloadIcon, MailIcon, TagIcon, Trash2Icon, UserPlusIcon } from "lucide-react"
import { BulkActionBar } from "@/components/ui/bulk-action-bar"import { Checkbox } from "@/components/ui/checkbox"
const invoices = [ { id: "INV-2104", customer: "Northwind Logistics", amount: "$5,900" }, { id: "INV-2103", customer: "Kestrel Health", amount: "$8,420" }, { id: "INV-2102", customer: "Halcyon Robotics", amount: "$11,227" }, { id: "INV-2101", customer: "Meridian Freight", amount: "$622" }, { id: "INV-2100", customer: "Lumen Analytics", amount: "$3,591" },]
export default function BulkActionBarInvoices() { const [selected, setSelected] = React.useState<string[]>(["INV-2104", "INV-2102"]) const [rows, setRows] = React.useState(invoices) const wait = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
return ( <div className="relative h-[380px] w-full max-w-2xl overflow-hidden rounded-xl border bg-card shadow-xs"> <ul className="divide-y"> {rows.map((r) => { const on = selected.includes(r.id) return ( <li key={r.id} data-selected={on || undefined} className="flex h-12 items-center gap-3 px-4 text-sm data-selected:bg-brand-subtle/40"> <Checkbox aria-label={`Select ${r.id}`} checked={on} onCheckedChange={(c) => setSelected((s) => (c ? [...s, r.id] : s.filter((x) => x !== r.id)))} /> <span className="w-20 font-mono text-[13px] text-muted-foreground">{r.id}</span> <span className="flex-1 truncate">{r.customer}</span> <span className="font-medium tabular-nums">{r.amount}</span> </li> ) })} </ul> <BulkActionBar count={selected.length} total={rows.length} noun={["invoice", "invoices"]} onClear={() => setSelected([])} onSelectAll={() => setSelected(rows.map((r) => r.id))} actions={[ { id: "remind", label: "Send reminders", icon: MailIcon, shortcut: "r", onAction: () => wait(900) }, { id: "assign", label: "Assign", icon: UserPlusIcon, shortcut: "a", onAction: () => wait(400) }, { id: "export", label: "Export", icon: DownloadIcon, shortcut: "e", onAction: () => wait(600) }, { id: "tag", label: "Add tag", icon: TagIcon, onAction: () => wait(300) }, { id: "archive", label: "Archive", icon: ArchiveIcon, disabled: "Paid invoices archive automatically after 90 days", onAction: () => {} }, { id: "delete", label: "Delete", icon: Trash2Icon, variant: "destructive", shortcut: "d", confirm: { title: `Delete ${selected.length} draft ${selected.length === 1 ? "invoice" : "invoices"}?`, description: "Customers never received them, so nothing changes on their side." }, onAction: async () => { await wait(700) setRows((rs) => rs.filter((r) => !selected.includes(r.id))) setSelected([]) }, }, ]} /> </div> )}Anatomy
<BulkActionBar count={selected.length} total={rows.length} onClear={clear} actions={[{ id: "archive", label: "Archive", shortcut: "e", onAction: archive }]}/>Guidance
Use it when
- Lists, tables and boards where people select several items and act on all of them.
- Three visible actions for the common ones, the rest in More.
- shortcut for actions people repeat, like archive or assign.
Avoid
- Actions for one item. Put those in the row menu.
- Hiding unavailable actions. Disable them with a reason so people learn why.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Render the bar inside the list's relative container with position floating, or inline above the list.
- Pass count and onClear; add total and onSelectAll to offer Select all.
- Make onAction async; the button shows a spinner and the others wait. Throw to show the error in the bar.
- Destructive actions confirm through Destructive confirm unless confirm is false. Put counts in the confirmation title.
- Shortcuts are single keys without modifiers and do nothing while people type in a field or a dialog is open.
API
BulkActionBar
| Prop | Type | Default | Description |
|---|---|---|---|
| count / total | number | Selected and selectable items. Hidden at 0. | |
| onClear / onSelectAll | () => void | Clear selection (also Escape) and select everything. | |
| noun | [string, string] | ["item", "items"] | Singular and plural for the count. |
| actions | BulkAction[] | { id, label, icon?, shortcut?, variant?, onAction, confirm?, disabled? }. | |
| maxVisible | number | 3 | Buttons before More. |
| position | "floating" | "inline" | "floating" | Over the bottom of the container, or in the flow. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add bulk-action-bar# or, with the registry in components.jsonnpx shadcn@latest add @prodular/bulk-action-bar