Skip to content
BigBang

Popular

    ↑↓Navigate↵OpenEscClose
    TR

    Patterns

    Bulk actions

    Selecting several rows and acting on them at once; when the bar shows, how the count is announced and how deleting is confirmed.

    A bulk action lets users apply one action to many rows in one step. This pattern fixes the selection, the action bar and how the result is announced.

    • When users need to apply the same action to many rows of a table or list.
    • When the actions make sense for all selected items: archive, export, delete.
    • For single-row actions use row actions; no bar is needed.

    Select rows. The bar appears with the first selection and shows the count. “Archive” clears the selection and reports the result in a toast with an “Undo” action. “Delete” first opens a confirmation dialog. Escape in the bar clears the selection.

    Live example, StorybookOpen in Storybook
    Item list
    Status
    Item 1Active50
    Item 2Pending100
    Item 3Draft150
    Item 4Active200
    Item 5Pending250
    Item 6Draft300
    Item 7Active350
    Item 8Pending400
    Deleting asks for a confirmation that shows the count on the button: “Delete 3 items”. Confirmation pattern
    import { ActionBar, AlertDialog, createDataTableColumnHelper, DataTable, Link, Stack, StatusBadge, Text, ToolbarButton, useToast, type DataTableRowSelection } from '@bigbang/ui';
    import { useMemo, useState } from 'react';
    import { type Lang, LangProvider } from '../lang';
    const copy = {
    tr: {
    caption: 'Öğe listesi',
    name: 'Ad',
    status: 'Durum',
    count: 'Adet',
    item: 'Öğe',
    archive: 'Arşivle',
    export: 'Dışa aktar',
    delete: 'Sil',
    selected: (count: number) => `${count} öğe seçildi`,
    archived: (count: number) => `${count} öğe arşivlendi`,
    exported: (count: number) => `${count} öğe dışa aktarıldı`,
    deleted: (count: number) => `${count} öğe silindi`,
    undo: 'Geri al',
    confirmTitle: (count: number) => `${count} öğe silinsin mi?`,
    confirmText: 'Seçili öğeler listeden kaldırılır. Bu işlem geri alınamaz.',
    confirmLabel: (count: number) => `${count} öğeyi sil`,
    cancel: 'Vazgeç',
    rule: (count: number) => `Silme, sayıyı düğmede gösteren bir onay ister: “${count} öğeyi sil”.`,
    ruleLink: 'Onay kalıbı',
    confirmHref: '/patterns/confirmation/',
    },
    en: {
    caption: 'Item list',
    name: 'Name',
    status: 'Status',
    count: 'Count',
    item: 'Item',
    archive: 'Archive',
    export: 'Export',
    delete: 'Delete',
    selected: (count: number) => `${count} selected`,
    archived: (count: number) => `${count} ${count === 1 ? 'item' : 'items'} archived`,
    exported: (count: number) => `${count} ${count === 1 ? 'item' : 'items'} exported`,
    deleted: (count: number) => `${count} ${count === 1 ? 'item' : 'items'} deleted`,
    undo: 'Undo',
    confirmTitle: (count: number) => `Delete ${count} ${count === 1 ? 'item' : 'items'}?`,
    confirmText: 'The selected items are removed from the list. This cannot be undone.',
    confirmLabel: (count: number) => `Delete ${count} ${count === 1 ? 'item' : 'items'}`,
    cancel: 'Cancel',
    rule: (count: number) => `Deleting asks for a confirmation that shows the count on the button: “Delete ${count} ${count === 1 ? 'item' : 'items'}”.`,
    ruleLink: 'Confirmation pattern',
    confirmHref: '/en/patterns/confirmation/',
    },
    };
    type Row = { id: string; name: string; status: 'active' | 'pending' | 'draft' | 'archived'; count: number };
    const statusOrder = ['active', 'pending', 'draft'] as const;
    const helper = createDataTableColumnHelper<Row>();
    /**
    * A table with row selection and an action bar. The bar appears with the first selected row and says how many are selected.
    * An action runs on all selected rows, clears the selection and reports the result in a toast; archive offers Undo.
    * Delete asks for confirmation first, with the count in the dialog title and on its button.
    */
    export function BulkActionsDemo({ lang = 'tr' }: { lang?: Lang }) {
    return (
    <LangProvider lang={lang}>
    <BulkTable lang={lang} />
    </LangProvider>
    );
    }
    function BulkTable({ lang }: { lang: Lang }) {
    const t = copy[lang];
    const toast = useToast();
    const [rows, setRows] = useState<Row[]>(() =>
    Array.from({ length: 8 }, (_, index) => ({ id: `row-${index + 1}`, name: `${t.item} ${index + 1}`, status: statusOrder[index % statusOrder.length] ?? 'draft', count: (index + 1) * 50 })),
    );
    const [selection, setSelection] = useState<DataTableRowSelection>({});
    const [confirmOpen, setConfirmOpen] = useState(false);
    const count = Object.keys(selection).length;
    const columns = useMemo(
    () => [
    helper.accessor('name', { header: t.name, meta: { rowHeader: true } }),
    helper.accessor('status', { header: t.status, enableSorting: false, cell: (context) => <StatusBadge status={context.getValue()} /> }),
    helper.accessor('count', { header: t.count, meta: { numeric: true } }),
    ],
    [t],
    );
    const archive = () => {
    const previous = new Map(rows.filter((row) => selection[row.id]).map((row) => [row.id, row.status]));
    setRows((current) => current.map((row) => (previous.has(row.id) ? { ...row, status: 'archived' } : row)));
    toast.show({
    title: t.archived(count),
    tone: 'success',
    action: { label: t.undo, onClick: () => setRows((current) => current.map((row) => (previous.has(row.id) ? { ...row, status: previous.get(row.id) ?? row.status } : row))) },
    });
    setSelection({});
    };
    const exportRows = () => {
    toast.show({ title: t.exported(count), tone: 'success' });
    setSelection({});
    };
    const deleteRows = () => {
    setRows((current) => current.filter((row) => !selection[row.id]));
    toast.show({ title: t.deleted(count), tone: 'success' });
    setSelection({});
    };
    return (
    <>
    <Stack gap="md">
    <ActionBar placement="inline" count={count} countLabel={t.selected} onClear={() => setSelection({})}>
    <ToolbarButton label={t.archive} icon="file" onClick={archive} />
    <ToolbarButton label={t.export} icon="download" onClick={exportRows} />
    <ToolbarButton label={t.delete} icon="trash" onClick={() => setConfirmOpen(true)} />
    </ActionBar>
    <DataTable<Row>
    columns={columns}
    data={rows}
    getRowId={(row) => row.id}
    getRowLabel={(row) => row.name}
    caption={t.caption}
    captionHidden
    selectionMode="multiple"
    rowSelection={selection}
    onRowSelectionChange={setSelection}
    // The action bar announces the count; the table stays quiet so it is read once.
    messages={{ 'selected-count': '' }}
    />
    <Text size="sm" tone="subtle">
    {t.rule(Math.max(count, 3))} <Link href={t.confirmHref}>{t.ruleLink}</Link>
    </Text>
    </Stack>
    <AlertDialog
    open={confirmOpen}
    onOpenChange={setConfirmOpen}
    title={t.confirmTitle(count)}
    description={t.confirmText}
    confirmLabel={t.confirmLabel(count)}
    cancelLabel={t.cancel}
    tone="danger"
    onConfirm={deleteRows}
    />
    </>
    );
    }
    Rule Value
    Bar Visible only while at least 1 row is selected.
    Placement placement="floating" (fixed to the bottom of the viewport) on long pages; placement="inline" when the table has a fixed area above it. The example uses inline.
    Number of actions 3 to 5. The most used action first, the destructive one last.
    Count “3 selected” in the bar; announced once to screen readers.
    Escape Clears the selection from inside the bar.
    Header checkbox Selects the rows of the visible page.
    All pages A “Select all 1,000 items” link is offered; it stays apart from the page selection.
    After an action The selection clears; the result is reported in a Toast: “3 items archived”. A reversible action offers “Undo” in the toast.
    Destructive action Asks for confirmation in an AlertDialog; the count is in the title and the button: “Delete 3 items?”, “Delete 3 items”. See Confirmation.
    DoThe destructive confirm button says the count.
    Don'tA button without a count leaves users unsure what they delete.
    3 items archived
    DoThe result says how many items were affected.
    Action completed
    Don'tA generic text leaves the effect unclear.
    1. The user selects a row or the header checkbox. The header checkbox selects only the visible page.
    2. The bar appears and shows the selected count. With several pages, a “Select all N items” link is offered.
    3. The user chooses an action. It applies to all selected rows.
    4. When the action finishes, the selection clears, the bar closes and a toast reports the result. For a reversible action such as archive, the toast offers “Undo”.
    5. Deleting asks for confirmation first: a danger AlertDialog opens with the count in the title and on the button. Focus starts on “Cancel”; if the user cancels, the rows and the selection stay. The dialog itself is defined in the Confirmation pattern.
    • The bar is a toolbar named “Bulk actions”; arrow keys move between the actions.
    • The selected count is read once, politely, from a status region. The table’s own selection announcement is turned off in the example, so the count is not read twice.
    • “Clear selection” is the first button and does the same as Escape.
    • Every row checkbox names its row: “Select row Item 1”.
    • The action result is announced politely in the “Notifications” region without moving focus (WCAG 4.1.3). Undo is a button inside the toast.
    • The confirmation dialog has the alertdialog role and does not close on an outside click.
    • Action names are verbs: “Archive”, “Export”, “Delete”.
    • The result gives the count and the verb: “3 items archived”.
    • The destructive confirm button carries the count: “Delete 3 items”.
    • The all-pages link gives the total: “Select all 1,000 items”.
    Item Motion Preset
    Bar appearing 240 ms, 8 px rise and fade motion.toast
    Count change 110 ms cross-fade; no counting animation motion.reveal
    Action buttons 0.97 scale on press motion.press

    With reduced motion, the shift is removed and only the fade stays.

    DataTable, ActionBar, Toolbar, Toast, AlertDialog, StatusBadge, Link, Button. Related patterns: Confirmation, Filtering, Data export.