PhiaUi.Components.BulkActionBar (phia_ui v0.1.17)

Copy Markdown View Source

BulkActionBar component for PhiaUI.

A contextual action toolbar that appears when one or more rows are selected in a table or list. Displays the selection count, a clear-selection button, and a slot for action buttons.

The toolbar is hidden automatically when count is zero — implemented as two function heads on bulk_action_bar/1: the count: 0 head renders an empty fragment so the DOM element is completely absent, while any non-zero count renders the full toolbar. This avoids CSS display:none tricks and keeps the DOM clean.

When to use

Use BulkActionBar in data table or list views wherever you want to let users select multiple rows and act on them collectively — for example:

  • Delete multiple records at once
  • Archive or restore a batch of items
  • Export selected rows to CSV
  • Assign multiple tasks to a team member
  • Approve / reject a group of requests

Anatomy

ComponentElementPurpose
bulk_action_bar/1divRoot toolbar (role="toolbar") — hidden when count=0
bulk_action/1buttonIndividual action button with optional icon and variant

Example — table with bulk actions

<%!-- Toolbar sits above (or below) the table and shows when rows are selected --%>
<.bulk_action_bar
  count={@selected_count}
  label="contacts selected"
  on_clear="clear_selection"
>
  <.bulk_action label="Send email" on_click="bulk_email" icon="mail" />
  <.bulk_action label="Export CSV"  on_click="bulk_export"  icon="download" />
  <.bulk_action label="Archive"     on_click="bulk_archive" icon="archive" />
  <.bulk_action label="Delete"      on_click="bulk_delete"  icon="trash" variant="destructive" />
</.bulk_action_bar>

<.data_grid rows={@contacts} columns={@columns} />

LiveView handlers

def handle_event("clear_selection", _params, socket) do
  {:noreply, assign(socket, selected_ids: MapSet.new(), selected_count: 0)}
end

def handle_event("bulk_delete", _params, socket) do
  ids = MapSet.to_list(socket.assigns.selected_ids)
  Repo.delete_all(from c in Contact, where: c.id in ^ids)
  {:noreply,
   socket
   |> assign(selected_ids: MapSet.new(), selected_count: 0)
   |> stream(:contacts, Repo.all(Contact))}
end

def handle_event("bulk_export", _params, socket) do
  ids = MapSet.to_list(socket.assigns.selected_ids)
  csv = Contacts.export_csv(ids)
  {:noreply, push_event(socket, "download", %{filename: "contacts.csv", content: csv})}
end

Why two function heads instead of CSS display:none?

When count is 0, bulk_action_bar/1 renders ~H"" — an empty HEEx fragment. This means the DOM element does not exist at all, which:

  • Prevents screen readers from announcing the toolbar when it is inactive
  • Avoids accidental tab focus landing on hidden buttons
  • Keeps the DOM tree small for large data tables
  • Makes role="toolbar" semantics correct (toolbars should not be present in the accessibility tree when they have no applicable context)

Summary

Functions

Renders a single bulk action button.

Renders the bulk action toolbar.

Functions

bulk_action(assigns)

Renders a single bulk action button.

Place inside a bulk_action_bar/1 slot. Use variant="destructive" for irreversible operations (delete, purge) so users visually distinguish them from safe operations (export, archive).

Example

<.bulk_action label="Delete selected" on_click="bulk_delete" icon="trash" variant="destructive" />
<.bulk_action label="Export"          on_click="bulk_export" icon="download" />

Attributes

  • label (:string) (required) - Button label text (e.g. "Delete", "Archive").

  • on_click (:string) (required) - phx-click event name fired when the button is pressed. The handler should act on all currently selected IDs and then clear the selection.

  • variant (:string) - Visual variant:

    • default — muted text with hover background (for safe operations)
    • destructive — red text-destructive with red hover (for delete/irreversible actions)

    Defaults to "default". Must be one of "default", or "destructive".

  • icon (:string) - Optional Lucide icon name displayed before the label. Recommended for quick recognition: "trash", "archive", "download", "mail".

    Defaults to nil.

  • class (:string) - Additional CSS classes for the button. Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the <button> element. Use disabled={@processing} to prevent double-submits during async operations. Supports all globals plus: ["phx-value-id", "phx-value-type", "disabled"].

bulk_action_bar(assigns)

Renders the bulk action toolbar.

When count is 0, renders an empty HEEx fragment (~H""), meaning no DOM element is present. This is preferable to CSS visibility toggling for accessibility and DOM cleanliness.

When count > 0, renders a glass-morphism toolbar with:

  • Selection count + label on the left
  • A clear (×) button to deselect all rows
  • A vertical divider
  • Action buttons from the :inner_block slot on the right

Uses role="toolbar" for correct ARIA semantics and includes a dynamic aria-label that reads the count aloud to screen readers.

Attributes

  • count (:integer) (required) - Number of currently selected items. When 0, the entire toolbar is removed from the DOM. When positive, the toolbar renders with count displayed prominently.

  • label (:string) - Label appended after the count, e.g. "selected" → "3 selected", or "contacts selected" → "5 contacts selected".

    Defaults to "selected".

  • on_clear (:string) (required) - phx-click event name for the clear-selection button. The handler should reset selected IDs and count to zero.

  • class (:string) - Additional CSS classes for the toolbar container. Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the root div.

Slots

  • inner_block (required) - bulk_action/1 children — the action buttons for this toolbar.