Selection bar
A floating bar with the count, bulk actions and a clear button while items are selected.
npx shadcn@latest add https://ui.goodtabs.co/r/selection-bar.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/selection-bar
2 selected
Tick rows to show the bar. It floats inside the list box and hides when nothing is selected.
With a destructive action
2 files
A solid destructive button next to a plain one, and a custom count label.
Compact
1 selected
The sm size with xs buttons, for dense lists and narrow panels.
Usage
import { SelectionBar } from "@/components/ui/selection-bar"
<div className="relative">
{/* the list */}
<SelectionBar count={selected.length} onClear={() => setSelected([])}>
<Button variant="ghost" size="sm" onClick={archive}>Archive</Button>
</SelectionBar>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| count | number | How many items are selected. The bar hides at 0. | |
| onClear | () => void | Clears the selection. Omit to hide the clear button. | |
| label | string | "{count} selected" | Count label template. {count} is replaced with the number. |
| clearLabel | string | "Clear selection" | Accessible name of the clear button. |
| position | "absolute" | "fixed" | "absolute" | absolute floats in the nearest positioned parent, fixed floats over the viewport. |
| size | "sm" | "default" | "default" | Bar height, 36px or 44px. |
| children | ReactNode | The actions, usually sm ghost buttons. |
Source
selection-bar/selection-bar.tsx
"use client";
import * as React from "react";
import { XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
type SelectionBarProps = React.ComponentProps<"div"> & {
/** How many items are selected. The bar hides at 0. */
count: number;
/** Clears the selection. Omit to hide the clear button. */
onClear?: () => void;
/** Count label. {count} is replaced with the number. */
label?: string;
/** Accessible name of the clear button. */
clearLabel?: string;
/** absolute floats in the nearest positioned parent, fixed floats over the viewport. */
position?: "absolute" | "fixed";
/** Bar height. sm is 36px, default is 44px. */
size?: "sm" | "default";
/** The actions, usually small ghost buttons. */
children?: React.ReactNode;
};
/**
* A floating bar at the bottom center that appears while something is selected.
* Shows the count, your actions and a clear button. Hidden, and inert, when count is 0.
*/
export function SelectionBar({
count,
onClear,
label = "{count} selected",
clearLabel = "Clear selection",
position = "absolute",
size = "default",
className,
children,
...props
}: SelectionBarProps) {
const open = count > 0;
const text = label.replace("{count}", String(count));
return (
<div
data-slot="selection-bar"
data-state={open ? "open" : "closed"}
inert={!open}
className={cn(
"inset-x-0 bottom-3 z-40 flex justify-center px-3 transition-[opacity,transform] duration-150 ease-out motion-reduce:transform-none",
position === "fixed" ? "fixed bottom-4" : "absolute",
open ? "opacity-100" : "pointer-events-none translate-y-2 opacity-0",
className,
)}
{...props}
>
<div
role="toolbar"
aria-label="Selection"
className={cn(
"flex max-w-full items-center gap-1 rounded-lg border bg-popover text-popover-foreground shadow-md",
size === "sm" ? "h-9 p-1" : "h-11 p-1.5",
)}
>
<span
aria-live="polite"
className={cn("shrink-0 px-2 font-medium tabular-nums", size === "sm" ? "text-xs" : "text-sm")}
>
{text}
</span>
{children && (
<>
<span aria-hidden="true" className="h-4 w-px shrink-0 bg-border" />
<div className="flex items-center gap-1">{children}</div>
</>
)}
{onClear && (
<Button
type="button"
variant="ghost"
size={size === "sm" ? "icon-xs" : "icon-sm"}
aria-label={clearLabel}
onClick={onClear}
className="shrink-0 text-muted-foreground"
>
<XIcon aria-hidden="true" />
</Button>
)}
</div>
</div>
);
}
For AI agents
Hand your agent this page, the markdown at /components/selection-bar/prompt.md, or the Copy AI prompt button above.
When to use
- Bulk actions on a list, table or grid of selectable rows
- When the actions should stay in reach while the person scrolls the selection
- Inbox, files and issues views where selecting is the main gesture
When not to use
- For a single item's actions: put them on the row or in a menu
- As a permanent toolbar; it is meant to come and go with the selection
- Inside a dialog or sheet that already has a footer of actions
Agent notes
- It takes onClear and the actions take onClick, so the component that renders it must be a client component ("use client")
- Defaults to position absolute: give the list wrapper relative and some bottom padding so the bar does not cover the last row; use fixed for page-level lists
- Hidden when count is 0 with a 150ms fade and 8px rise, and inert so nothing inside it can be tabbed to
- For a destructive action use a solid bg-destructive text-background button, not the soft destructive variant