developer guide
Full-Width Rows in a JavaScript Data Grid
A full-width row spans every column, which is where a banner, a section break, an empty-state message or a summary belongs. You draw it, the grid still virtualises it, and column groups can be formed and renamed while the reader works.
Developer guide › Columns and cell rendering › Full-Width Rows in a JavaScript Data Grid
Full-width rows
A row drawn as a single band across every column instead of being divided into them: a section banner, an explanatory note, an empty-group message, a “load more” affordance, anything that belongs between rows and is not itself divided by the columns.
A banner before each section
createGrid(element, {
columns,
rows, // your data, with the banners in it
fullWidth: {
when: (row) => row.data.kind === 'section',
render: ({ data }) => data.title,
},
});
render returns a string for text or a node for content, or returns nothing and
writes into params.element itself. An HTML string is deliberately not accepted.
params carries { row, data, index, grid, element }.
The band holds still while the columns scroll under it, which is what a banner is for: text that scrolled sideways out of view with the columns would be a worse version of a cell. It is drawn over the pinned regions as well as the centre, so it genuinely spans every column.
A full-width row is still one of your data rows. It is counted by
rows.count(), sorted, filtered and exported like any other; only its
presentation changes. That is the difference between this and
pinned rows, and it is the thing to get straight before choosing
between them: full-width changes how a row looks, pinned changes whether a
row is data at all.
So if your banners must not appear in an export or a row count, they should not be in the data. If they are section headings that belong with the records they head, and should sort, filter and export alongside them: this is the right tool.
One consequence worth stating plainly: sorting reorders banners along with everything else, because the predicate follows the row and not its position. Either do not offer sorting on such a grid, or sort on a key that keeps each section together.
A band is exposed as a row containing one cell with aria-colspan covering every
column, so a screen reader reads it as one wide cell rather than as a row with missing ones.
No second, empty row is rendered underneath it.
Forming banded headers at runtime
Banded headers can be declared in config (columnGroups) and now also formed,
renamed, moved and dissolved at runtime through grid.columns, with a keyboard
equivalent for every action. The model is the single source of truth: a band made by
interaction is the same ColumnGroup tree config drives, and it round-trips through
a saved view.
The API. groupColumns(ids, { title, groupId }) wraps columns
in a new band or adds them to an existing one; pass id instead of
groupId to create a new band with a caller-chosen, stable id you can address
later (groupColumns(ids, { title: 'Traffic', id: 'g-traffic' })).
ungroupColumn(id) takes a column
out (dissolving a band it empties); renameGroup(id, title),
dissolveGroup(id) and moveGroup(id, to) do the rest. Each emits
columngroup:changed. A band's columns are always contiguous, and a nested band
dissolves into its parent, not the root.
The keyboard (WCAG 2.1.1). From a focused header cell: Ctrl+Shift+←/→ groups the column with its neighbour on that side (joining an adjacent band, or forming a new one); Ctrl+Shift+↑ takes it out of its band; and Alt+Shift+←/→ moves the whole band as a unit. Every action is announced through the live region, and a refusal - "not in a band", "cannot be moved there" - is announced too, never silent.
Pinning and visibility. A band lives in one pin region and draws over its
visible columns there: hiding a column shrinks the band's span without changing the band
definition, and hiding the last visible column hides the band. A band is exposed to assistive
technology as one role="columnheader" cell with an aria-colspan.