RowSelectActionBar
A floating bar that appears when table rows are selected. It shows the count and the actions for the selection.
1 selected
| Repository | Visibility | |
|---|---|---|
| repo-1(opens in new tab) | Public | |
| repo-2(opens in new tab) | Private | |
| repo-3(opens in new tab) | Private | |
| repo-4(opens in new tab) | Public |
<template> <RowSelectActionBar :count="selectedIds.length" :actions="[ { label: 'Export', icon: 'i-lucide-download', onClick: onBulkExport }, { label: 'Delete', icon: 'i-lucide-trash', destructive: true, onClick: onBulkDelete }, ]" @clear="rowSelection = {}" /></template>Props
| Name | Type | Default |
|---|---|---|
| count | number | - |
| actions | RowSelectAction[] | - |
| clearLabel | string | "Clear" |
| moreLabel | string | "More" |
| maxVisibleActions | number | 2 |
| countLabel | (count: number) => string | count => `${count} selected` |
Slots
| Name | Description |
|---|---|
| count-trailing | Rendered right after the count label, outside its live region, e.g. a "Select all N matching" link-button. |
| action-item-leading | Scoped `{ action, item }`. Replaces an inline picker action's option leading icon (e.g. a status dot). Not applied once that picker overflows into "More". |
Guidelines
Do
- Pair it with
Table'screateRowSelectColumn- own therowSelectionstate yourself (aRecord<string, boolean>keyed by row id, same asv-model:row-selectiononTable) and pass its selected count in; this component only renders count + actions, it never reads or owns selection state itself. - Set
Table'sgetRowId(e.g.getRowId: row => row.id) whenever the paired table paginates - see table.md's own Do note oncreateRowSelectColumn. Without a stable row id, the selection this bar reports can silently apply to the wrong rows once the table's data changes page. - Pass
destructive: trueon a delete/remove action so it renders with Button's destructive variant instead of secondary.
- Keep
FilterBarrendered as-is while rows are selected - search and filtering should stay usable mid-selection, matching how Linear/Notion/Gmail's own bulk-selection bars leave their search/filter chrome in place rather than hiding it. - It's
fixedat the bottom center of the viewport internally, hugging its own content width rather than the page's, but never wider than the viewport (on a narrow screen its controls wrap onto more lines) - no extra wrapper or positioning needed, and it stays visible regardless of scroll since it isn't document-flow-relative. While it's up it also renders an empty in-flow block as tall as the bar, so place it right after the table footer: the page can then scroll its last content (Pagination) clear of the bar. actionsisn't Button-only - settype: 'select'/type: 'combobox'on an action (alongsideitems/modelValue/onChange, mirroringSelect/Combobox's own props) for a bulk picker (e.g. "Change status", "Assign owner") instead of a push-button. Plain actions default totype: 'button'and need notypeat all.- Only the first
maxVisibleActions(2 by default) non-destructive actions render inline; put whichever ones matter most (and anycomboboxaction, since its search box is lost once collapsed) among them - the rest collapse into a trailing "More" dropdown automatically. - Raise
maxVisibleActionswhen the inline actions only make sense side by side - e.g. two pickers plus the Apply button that commits them needs:max-visible-actions="3", since an Apply tucked into "More" reads as unrelated to the pickers it applies. - Set
loading: trueon a button action while its async bulk mutation runs - it shows Button's spinner and ignores clicks, so a slow request can't be fired twice. Usedisabled: truefor an action that doesn't apply to the current selection. Both carry into "More": the item is disabled there (no spinner). - Put a selection-scope control in the
#count-trailingslot (e.g. avariant="plain"Button: "Select all 240 matching" / "Clear selection of all 240") - it renders right after the count, outside the count'srole="status"live region, so screen readers announce only the count change. - Use the
#action-item-leading="{ action, item }"slot for a status dot in an inline picker action's options. A picker that overflows into "More" becomes a plain submenu and doesn't get it, so keep it inline (raisemaxVisibleActions) when the dot matters. Once filled, the slot replaces the leading icon of every inline picker's options, so branch onactionand renderitem.iconyourself for the others.
Don't
- Don't build your own Clear control - it's built in (a ghost IconButton "x") and emits
clearfor you to reset selection. - Don't render it conditionally yourself - it already renders nothing when
countis 0. - Don't hide
FilterBarwhile this is showing - the two occupy different parts of the screen now (floating bottom bar vs. top row) and are meant to coexist.
- Don't pass more non-destructive actions than
maxVisibleActionsexpecting them all inline - anything past that moves into "More" on its own; acomboboxaction there degrades to a plain (non-searchable) submenu of its own items. - Don't raise
maxVisibleActionsjust to avoid "More" - only when the inline actions work as one group. Past 3 the bar stops reading as a compact floating bar. - Don't expect a
destructive: trueaction to ever render inline, however highmaxVisibleActionsis - it always goes into "More", so it can't be fired by a stray click on the bar. - Don't track an action's in-flight state with your own spinner or by removing the action - pass
loadingand let the bar render it.
Examples
Loading action
An async bulk mutation shows a spinner and blocks repeat clicks until it settles.
3 selected
<template> <RowSelectActionBar :count="3" :actions="[ { label: 'Export', icon: 'i-lucide-download', loading: exporting, onClick: onExport }, { label: 'Archive', icon: 'i-lucide-archive', disabled: exporting, onClick: onArchive }, ]" @clear="rowSelection = {}" /></template>Select all matching
A selection-scope link after the count, for widening a page selection to every matching row.
25 selected
<template> <RowSelectActionBar :count="allMatchingSelected ? total : pageSelectedCount" :actions="actions" @clear="clearSelection"> <template #count-trailing> <Button v-if="!allMatchingSelected" variant="plain" size="sm" @click="allMatchingSelected = true"> Select all {{ total }} matching </Button> <Button v-else variant="plain" size="sm" @click="allMatchingSelected = false"> Clear selection of all {{ total }} </Button> </template> </RowSelectActionBar></template>Three inline actions
Two pickers plus the Apply button that commits them, kept side by side with maxVisibleActions.
4 selected
<template> <RowSelectActionBar :count="4" :max-visible-actions="3" :actions="[ { type: 'select', label: 'Status', items: statusItems, modelValue: status, onChange: v => (status = v) }, { type: 'select', label: 'Severity', items: severityItems, modelValue: severity, onChange: v => (severity = v) }, { label: 'Apply', disabled: !status && !severity, onClick: onApply }, ]" @clear="rowSelection = {}" /></template>