Skip to main content

Table

Displays structured data in rows and columns with support for sorting, pagination, filtering, and row selection. Use for dense data sets that require comparison or scanning. Don't use for simple key-value data.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Grid element Required The wa-data-grid element itself. Takes a label (accessible name), a row-key (the field uniquely identifying each row), and .data / .columns set as JavaScript properties — not HTML attributes, since both are arrays/objects.
Column definition Required Each entry in .columns maps a field to a header label, with optional sortable, width/flex, align, and a formatter function for custom cell content (badges, buttons, computed values).
Header row Required Rendered from column definitions. Shows sort indicators, optional per-column filter controls, and a per-column menu (pin/sort/hide/autosize) when with-column-menu is set.
Selection checkbox column Optional Added automatically when selectable is set. Includes a header select-all checkbox with indeterminate state handled internally.
Actions column Optional A column with no field, just an id and a formatter rendering buttons/links/menus for row-level actions. Give each icon-only action button its own accessible name.
Empty / loading / no-results slots Optional Named slots (empty, loading, no-results) for custom content when there's no data, a server request is in flight, or an active search/filter matches nothing.
Pager Optional Rendered automatically in the footer when paginate is set — a wa-pagination element under the hood, stylable via the pager CSS part.

Variants

States

State Behaviour
Default Rows render with the standard grid surface and row divider. Sortable columns show an inactive sort affordance in the header.
Row hover Hovered row background shifts to indicate focus of attention. Does not affect sort or selection state.
Row selected Selected rows are visually distinguished and their checkbox is checked. selectedKeys / selectedRows are the source of truth; read them or listen for wa-row-select.
Column sorted (ascending / descending) The active column's header shows a directional sort indicator. Third click on a sorted column clears the sort by default (set without-sort-removal to keep it always sorted, alternating direction instead).
Loading In server mode, set the loading property (or let the grid manage it automatically while dataSource resolves). Slot custom content into the loading slot for a bespoke overlay.
Empty When data is an empty array, the empty slot's content renders in place of rows. Provide an explanation and, where relevant, a call to action (e.g. "Create engagement").
No results When an active search or column filter matches nothing, the no-results slot renders instead of the empty slot — falls back to a localized default message if not provided.
Row locked / non-selectable Set selectableRows to a predicate returning false for rows that shouldn't be selectable — their checkbox is disabled and they're skipped by select-all and range selection.

Usage guidelines

Do / Don't

Do

Always set the label attribute so assistive technology has an accessible name for the grid.

Don't

Don't rely on surrounding heading text alone to label the grid — a heading isn't programmatically associated with it.

Do

Set row-key to a stable, unique field (e.g. a database id) whenever selectable or pagination is used, so selection survives sorting and page changes.

Don't

Don't use an array index or a field that can repeat as row-key — selection and expansion state will behave unpredictably.

Do

Keep column count to a maximum of seven or eight on desktop. Prioritise the most important attributes and use a row detail panel or a separate page for secondary data.

Don't

Don't add more columns to avoid building a detail page — overly wide grids force horizontal scrolling and overwhelm users.

Do

Show meaningful empty and no-results slot content, explaining what happened and how to adjust.

Don't

Don't leave the empty/no-results slots unset when the default message won't make sense for your data — users can't tell whether data is missing, filtered out, or still loading.

Do

Place bulk action controls (e.g. Export selected, Change status) directly above or below the grid, enabled only when rows are selected.

Don't

Don't trigger destructive bulk actions immediately on selection — always require an explicit confirm step.

Do

Give every icon-only action-column button its own accessible name that includes the row subject, e.g. "Edit Draft Transport Strategy".

Don't

Don't use a bare "Edit" or "Delete" label on row actions — a screen reader user scanning many rows can't tell which record each button acts on.

Layout & Spacing

Row height, cell padding, and text scale all move together via the size attribute (xs, s, m, l, xl — default m). Column widths: set width for a fixed pixel width, or flex/minWidth to share remaining space proportionally. Unset columns share space equally. Grid border radius and outer spacing come from wa-data-grid's own --wa-* panel tokens (see CSS Custom Properties in Web Awesome's docs) — Orbit hasn't layered semantic --op-* overrides onto this component yet.

Tokens

PartTokenValue
Row/body surface (outlined) --wa-color-surface-default → scoped to --wa-color-surface-raised Same underlying token family as wa-card's outlined appearance, and scoped to raised the same way — via component-overrides.css, not the global token every other surface-default component also reads. The header uses its own --header-background (already --wa-color-surface-lowered by default), so it stays visually distinct from the raised rows.
Header text --wa-color-text-quiet Slightly subdued relative to data cell text to create visual hierarchy, consistent with Orbit's header-vs-body text convention elsewhere.
Data cell text --wa-color-text-normal Full-weight text for scannable data content.
Selected/active row accent --wa-color-brand-fill-loud (or equivalent) Verify against the current token set once a real Open Point or Social Point screen adopts this component — no Orbit-specific override exists yet, unlike wa-card's surface scoping.
Focus ring --op-color-interactive-focus Should match every other interactive element in Orbit. Confirm the grid's internal focus-ring token maps to this rather than a generic browser default.

Engineering notes

  • Use the wa-data-grid element. Set .data and .columns as JavaScript properties (not HTML attributes) — they're arrays/objects, not strings.
  • Always set row-key to a stable unique field. Without it, selection and expansion state can't be tracked reliably across sorts, filters, and pages.
  • A column needs a field to participate in sorting, filtering, search, copy, and CSV export. An actions column has no field — just an id and a formatter — and is correctly excluded from all of those.
  • formatter receives (value, row) and can return a plain string (escaped automatically) or a lit html template for rich content (badges, buttons, links). Never build cell HTML via string concatenation — use the lit html tag so interpolated values stay escaped.
  • Client-side sorting/filtering/pagination is automatic. For large or server-backed data sets, set dataSource (an async function) or server + listen for wa-data-request — the grid switches to manual mode and you control what data.length /.total the pager reflects.
  • Don't reimplement sort/selection/pagination with a hand-rolled — this component exists specifically so teams stop doing that per-screen, each with slightly different accessibility gaps.

    Keyboard interaction

    KeyAction
    Tab / Shift+Tab Moves focus into or out of the grid as a single stop (not per-cell) — the grid manages internal focus with a roving tabindex, per the ARIA grid pattern.
    Arrow keys Once the grid has focus, move between cells (including into the header row).
    Home / End Move to the first / last cell in the current row.
    Ctrl+Home / Ctrl+End Move to the first / last cell in the entire grid.
    Page Up / Page Down Move by a page of rows.
    Enter Sorts the focused header column, toggles a row's expansion from its expand cell, or activates a data cell (emits wa-cell-click).
    Space Toggles selection of the focused row.
    Shift+Up / Shift+Down Extends the selection.
    Ctrl+A Selects all rows (the current page's rows, when paginated).
    Ctrl+C Copies the selected rows to the clipboard.
    Shift+Left / Shift+Right Reorders the focused header column (when reorderable).
    Alt+Left / Alt+Right Resizes the focused header column (when resizable).

    Why it matters

    Government datasets often include sensitive information about individuals — stakeholders, submitters, community members. A grid that lacks proper roving-focus and ARIA grid semantics forces assistive technology users into a much slower, error-prone workflow to review or act on records, which is a genuine compliance risk under the Disability Discrimination Act and equivalent legislation elsewhere. Building this once, correctly, in the underlying component is what makes every screen that uses it accessible by default rather than by individual implementation effort.

Focus

The grid is a single tab stop; once focused, arrow keys move a roving internal focus between cells (never re-entering Tab order per cell). Sorting, filtering, selecting, and paging never move focus outside the grid unexpectedly — focus stays on the control that was activated (a header cell, a checkbox, a pager button). Row selection state (selectedKeys / selectedRows) and view state (sort, filters, column order/widths/visibility) can be captured with getState() and restored with setState() — useful for preserving a user's view across navigation.

ARIA

Role or attributeWhen to useExample
role="grid" (or "treegrid") Applied to the component root automatically. treegrid is used when rows have children (tree data via childRows). <wa-data-grid label="Stakeholder register" row-key="id"></wa-data-grid>
label The grid's accessible name. Always set it — there is no visible caption element to fall back on. <wa-data-grid label="Consultation submissions" row-key="id"></wa-data-grid>
aria-label (action buttons) Every icon-only button in an actions-column formatter needs its own accessible name including the row subject. <wa-button size="s" appearance="plain"><wa-icon name="pencil" label="Edit Draft Transport Strategy"></wa-icon></wa-button>
Live region announcements The grid announces selection counts, filter/search results, clipboard copies, and column moves through its own polite live region — no additional markup required. (handled internally by the component)

Contrast

Header text against the grid surface, and data cell text against the row background, both need to meet 4.5:1 — verify once Orbit's own token overrides are applied to this component (none exist yet, see the Style tab). Any status badge rendered inside a formatter follows Badge's own contrast rules. Focus indicators on cells and header controls must meet the same 3:1 non-text contrast requirement as every other interactive element in Orbit.

Touch targets

Selection checkboxes, per-column menu buttons, sort headers, and action-column buttons all need to meet the 44x44px minimum touch target on any surface that supports touch. The compact (xs/s) size variants trade touch comfort for density — reserve them for desktop-only, mouse/keyboard operational views.

Things to avoid

  • Do not hand-roll a semantic <table> alongside wa-badge/wa-button cell content to replicate what this component already does — that was Orbit's own pre-3.11 stopgap and is exactly the gap this component closes.
  • Do not rely on colour alone to communicate sort direction or row selection — the grid pairs colour with an icon/indicator by default; don't strip that in custom styling.
  • Do not give an actions-column formatter's buttons generic labels — see callout_watch.
  • Do not use row-key values that can collide or change across a session (e.g. an array index) — selection, expansion, and sort stability all depend on it being a genuinely stable identifier.

Was this page helpful?

Updated 5 October 2026