busy-office-ui
Menu

Table toolbar, columns & grid navigation

The two opt-in behaviors that sit on top of a data table: a toolbar with column visibility and export, and the keyboard grid navigation model. Both are deliberate opt-ins. Do not add either to a read-mostly list — a reference table someone scans a few times a day gains nothing from column toggles (its columns were chosen server-side for a reason), and keyboard grid navigation actively costs: the whole table collapses to one Tab stop, and every control inside it — here the select-all and each row checkbox — is pulled out of the Tab sequence, reachable only by arrowing to its cell and pressing Enter. A table a screen-reader user could browse natively becomes a single widget they must learn to operate. The plain data table already ships selection, sort and filter free; earn these two with a real need — many optional columns, or genuinely cell-level work.

Toolbar — column visibility & export opt-in

Invoice # Vendor Cost center Amount Status
INV-10234 Acme Supply Co. CC-4021 $4,208.00 Pending
INV-10235 Globex Industrial CC-4021 $18,940.50 Approved
INV-10236 Initech GmbH CC-1180 $730.25 Rejected

Columns is the same multi-select dropdown pattern as elsewhere (data-multiselect, real checkboxes), so it needs initDropdowns() alongside initTableToolbar() — the toolbar behavior owns show/hide and the export intent, the dropdown behavior owns the menu (anchoring it to its button, staying open across selections, and the count on the trigger label) — checking/unchecking a data-col-toggle box shows/hides every <th>/<td> with the matching data-col, scoped to this table only. If you put column toggles on a data-grid-nav table — these two demos are separate tables, so you cannot try it here — a hidden column is dropped from the grid model too: the cell cursor skips it, and hiding the column the cursor is parked in hands the grid's single tab stop to a visible cell rather than stranding it somewhere Tab cannot reach. Export dispatches bo:table-export with a format — this behavior only tracks the intent, generating and downloading the actual file is your code (fetch a server export endpoint, or build a client-side CSV/PDF) — same split as bo:row-save and bo:scan. Try it: uncheck a column, then click Export.

Advanced — keyboard grid navigation opt-in

The second tier: data-grid-nav + initDataGrid() turns the table into a WAI-ARIA APG "Grid": role="grid" (cells get their gridcell/columnheader roles implicitly — no per-cell markup needed), aria-selected synced from the row checkboxes, and two-axis roving tabindex — Tab enters the grid at ONE cell, arrow keys move the cursor (clamped, no wrap), Home/End jump to the row's first/last cell and Ctrl+Home/Ctrl+End to the grid's, Enter focuses a cell's single widget (the checkbox), Esc hands focus back to the cell. Reach for this when a screen is genuinely spreadsheet-like (dense data entry, heavy keyboard use) — the Simple tier above is enough for most tables, including ones with sort and bulk-select. Click into the table below, then try arrow keys.

Invoice # Amount
INV-10234 $4,208.00
INV-10235 $18,940.50
INV-10236 $730.25
<table class="bo-data-table" data-grid-nav>
  <thead><tr>
    <th scope="col"><input type="checkbox" class="bo-checkbox bo-data-table__select-all" aria-label="Select all" /></th>
    <th scope="col">Invoice #</th>
    <th scope="col" class="bo-data-table__col--numeric">Amount</th>
  </tr></thead>
  <tbody><tr>
    <td><input type="checkbox" class="bo-checkbox bo-data-table__row-select" aria-label="Select INV-1" /></td>
    <td class="bo-data-table__col--code">INV-1</td>
    <td class="bo-data-table__col--numeric">$4,208.00</td>
  </tr></tbody>
</table>
<script type="module">
  import { initDataGrid } from "@busy-office/ui/js";
  initDataGrid(); // role=grid, two-axis roving tabindex, Enter/Escape into-and-out-of a cell's widget
</script>
↑↓ move Enter open Esc close