Skip to content
goodtabs UI
BrowseBrowse components

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

Tick rows to show the bar. It floats inside the list box and hides when nothing is selected.

With a destructive action

A solid destructive button next to a plain one, and a custom count label.

Compact

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

PropTypeDefaultDescription
countnumberHow many items are selected. The bar hides at 0.
onClear() => voidClears the selection. Omit to hide the clear button.
labelstring"{count} selected"Count label template. {count} is replaced with the number.
clearLabelstring"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.
childrenReactNodeThe 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