{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bulk-action-bar",
  "title": "Bulk Action Bar",
  "description": "The bar that appears once rows are selected in a table or list — it shows \"3 items selected\", holds your bulk actions (delete, archive, export, assign, move, approve, mark as read) as children, and gives the user a way out of selection mode. Use it with any multi-select surface: a data table with row checkboxes, an admin users/orders/invoices list, a file manager or media library, an inbox, a moderation queue, or a mobile-style edit mode. Common asks it answers: \"bulk action bar\", \"bulk actions toolbar\", \"selection toolbar\", \"batch actions\", \"n selected bar\", \"contextual action bar\", \"what to show when table rows are checked\". shadcn/ui ships no such component — its data-table recipe leaves selected-row UI entirely to you; this packages the part everyone rewrites. It hides itself at count 0, so you render it unconditionally and just pass the selected count. The count is announced to screen readers from a live region that stays mounted even at zero (a live region inserted together with its text is not reliably announced, so a bar that unmounts completely would swallow the first update), and the visible count is aria-hidden to avoid a double read. Escape clears the selection, ignoring already-handled key presses so a dialog opened from one of your actions still closes normally. It is a labelled region, not an ARIA toolbar, because a toolbar is expected to implement roving-tabindex arrow navigation and claiming the role without it reads worse than plain tab order. Pass variant=\"floating\" (default) for a pinned bar above the page bottom or \"inline\" to sit in the flow above the table; itemName/itemNamePlural handle the noun and irregular plurals. Styled with shadcn tokens (background, border, muted-foreground, accent, ring) for automatic light/dark theming, with zero dependencies beyond your cn util. Distinct from toast, which reports a result after the fact; this one hosts the actions themselves.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/ui/bulk-action-bar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface BulkActionBarProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /** How many rows/items are currently selected. The bar hides itself at 0. */\n  count: number\n  /** Singular noun for the selected things, e.g. \"row\", \"file\", \"invoice\". */\n  itemName?: string\n  /** Plural noun. Defaults to `itemName` + \"s\"; set it for irregular plurals. */\n  itemNamePlural?: string\n  /** Clears the selection. Also wired to Escape and the trailing button. */\n  onClear?: () => void\n  /** Label of the clear button. */\n  clearLabel?: string\n  /** `floating` pins the bar above the page bottom; `inline` sits in the flow. */\n  variant?: \"floating\" | \"inline\"\n  /** Accessible name of the bar's landmark region. */\n  label?: string\n}\n\n/**\n * The action bar that appears once rows are selected in a table or list: it\n * shows how many are selected, hosts the bulk actions (delete, export, assign)\n * as children, and offers a way back out of selection mode.\n *\n * The count is announced to screen readers from a live region that stays\n * mounted even at 0 — a live region inserted together with its text is not\n * reliably announced, so hiding the whole bar would swallow the first update.\n *\n * It is a labelled `region` rather than a `toolbar`: an ARIA toolbar is\n * expected to implement roving-tabindex arrow navigation, and claiming the role\n * without it reads worse to assistive tech than plain tab order.\n */\nexport function BulkActionBar({\n  count,\n  itemName = \"item\",\n  itemNamePlural,\n  onClear,\n  clearLabel = \"Clear\",\n  variant = \"floating\",\n  label = \"Bulk actions\",\n  className,\n  children,\n  ...props\n}: BulkActionBarProps) {\n  const noun = count === 1 ? itemName : (itemNamePlural ?? `${itemName}s`)\n  const summary = `${count} ${noun} selected`\n\n  React.useEffect(() => {\n    const clear = onClear\n    if (count <= 0 || !clear) return\n    const onKeyDown = (event: KeyboardEvent) => {\n      // Skip handled presses so Escape can still close a dialog or dismiss a\n      // combobox opened from one of the actions without wiping the selection.\n      if (event.key !== \"Escape\" || event.defaultPrevented) return\n      clear()\n    }\n    document.addEventListener(\"keydown\", onKeyDown)\n    return () => document.removeEventListener(\"keydown\", onKeyDown)\n  }, [count, onClear])\n\n  return (\n    <>\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {count > 0 ? summary : \"\"}\n      </span>\n      {count > 0 ? (\n        <div\n          role=\"region\"\n          aria-label={label}\n          className={cn(\n            \"flex items-center gap-3 border bg-background text-foreground\",\n            variant === \"floating\"\n              ? \"fixed inset-x-0 bottom-6 z-50 mx-auto w-fit max-w-[calc(100%-2rem)] rounded-lg px-3 py-2 shadow-lg\"\n              : \"w-full rounded-md px-3 py-2\",\n            className\n          )}\n          {...props}\n        >\n          <span aria-hidden=\"true\" className=\"text-sm font-medium tabular-nums\">\n            {summary}\n          </span>\n          <div className=\"flex flex-wrap items-center gap-2\">{children}</div>\n          {onClear ? (\n            <button\n              type=\"button\"\n              onClick={onClear}\n              className=\"ml-auto inline-flex h-8 shrink-0 items-center rounded-md px-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n            >\n              {clearLabel}\n            </button>\n          ) : null}\n        </div>\n      ) : null}\n    </>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}