Data Grid
A spreadsheet-grade grid for working with tabular data: inline cell editing, multi-column sorting, pinned columns, row selection, and virtualized rendering. Columns are defined as a JavaScript array, and the grid implements the full WAI-ARIA grid keyboard pattern with a single tab stop.
<arc-data-grid> Overview
>
DataGrid is the tier above DataTable — reach for it when users need to *work* with the data rather than just read it. Columns are configured through a `columns` array property (not child elements), where each entry can opt into sorting, inline editing, alignment, a fixed width, and left-edge pinning.
Sorting is multi-column: clicking a sortable header cycles ascending → descending → off, and Shift+clicking appends the column as a secondary sort. When more than one sort is active, each sorted header shows its direction arrow plus a priority number. The grid sorts a copy of your data internally and also emits `arc-sort` with the full sort array — set `manual-sort` to skip internal sorting and drive it from a server instead.
Editable columns turn cells into inline editors: press Enter or double-click a cell to open a token-styled input, Enter or blur commits (emitting `arc-cell-change`), Escape cancels. The grid mutates only its own display copy — your source array stays untouched, so you remain the owner of the data. Pinned columns stay stuck to the left edge during horizontal scroll with an elevation shadow, and the `virtual` mode renders only visible rows for large datasets.
Keyboard support follows the WAI-ARIA grid pattern: one tab stop for the whole grid, arrow keys move a roving cell focus, Home/End jump to row ends, Ctrl+Home/End jump to the grid corners, Enter activates (sorts a header, edits a cell), and Space toggles row selection.Guidelines
When to use
- Use DataGrid when users edit, multi-sort, or bulk-select data; use DataTable for read-mostly display
- Listen to arc-cell-change and write edits back to your own data store — the grid only updates its display copy
- Set manual-sort and handle arc-sort yourself when the dataset is paginated or sorted server-side
- Pin only one or two key identifier columns (IDs, names) so unpinned data stays readable
- Give pinned columns an explicit width — pinned offsets are computed from column widths
- Enable virtual with an accurate row-height for datasets beyond a few hundred rows
When not to use
- Do not mark every column editable — restrict editing to fields users genuinely need to change inline
- Do not rely on the grid to persist edits; it never mutates the rows array you passed in
- Do not pin so many columns that unpinned content has no room on narrow screens
- Do not mix virtual mode with rows of varying heights — virtualization assumes a fixed row-height
- Do not nest complex interactive components (modals, drawers) inside grid cells
- Do not use DataGrid for simple read-only lists — DataTable or List are lighter choices
Features
- Column configuration via a plain JavaScript array — width, alignment, sortable, editable, pinned per column
- Multi-column sorting: click cycles asc/desc/none, Shift+click adds secondary sorts with priority indicators
- manual-sort mode for server-side sorting driven by the `arc-sort` event
- Inline cell editing with Enter/double-click to open, Enter/blur to commit, Escape to cancel
- Grid mutates only its internal display copy — consumer data stays the source of truth
- Pinned columns stick to the left edge with an elevation shadow while scrolling horizontally
- Row selection with select-all checkbox including indeterminate state
- Virtualized rendering for large datasets via the virtual and row-height props
- Full WAI-ARIA grid keyboard pattern: roving cell focus, one tab stop, arrow/Home/End/Ctrl navigation
- `arc-sort`, `arc-cell-change`, and `arc-select` custom events
- Sticky header row that stays visible during vertical scroll
- CSS custom property theming via ARC design tokens, plus ::part hooks for table, header, row, cell, and editor
Preview
| No data available |
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-data-grid id="positions-grid" selectable></arc-data-grid>
<script type="module">
import '@arclux/arc-ui/data-grid';
const grid = document.querySelector('#positions-grid');
grid.columns = [
{ key: 'ticker', label: 'Ticker', width: '90px', pinned: true, sortable: true },
{ key: 'name', label: 'Company', width: '180px', sortable: true },
{ key: 'sector', label: 'Sector', sortable: true },
{ key: 'price', label: 'Price', align: 'right', sortable: true, editable: true },
{ key: 'change', label: 'Change %', align: 'right', sortable: true, editable: true }
];
grid.rows = [
{ ticker: 'ARC', name: 'Arclight Systems', sector: 'Technology', price: 142.5, change: 2.4 },
{ ticker: 'LUM', name: 'Lumen Dynamics', sector: 'Energy', price: 87.2, change: -1.1 },
{ ticker: 'NVA', name: 'Nova Materials', sector: 'Industrials', price: 56.8, change: 0.7 }
];
grid.addEventListener('arc-cell-change', (e) => {
const { rowIndex, key, value } = e.detail;
// write the edit back to your own data store
console.log('edited', rowIndex, key, value);
});
grid.addEventListener('arc-sort', (e) => console.log('sort', e.detail.sort));
grid.addEventListener('arc-select', (e) => console.log('selected', e.detail.selectedIndices));
</script> import { DataGrid } from '@arclux/arc-ui-react';
const columns = [
{ key: 'ticker', label: 'Ticker', width: '90px', pinned: true, sortable: true },
{ key: 'name', label: 'Company', width: '180px', sortable: true },
{ key: 'sector', label: 'Sector', sortable: true },
{ key: 'price', label: 'Price', align: 'right', sortable: true, editable: true },
{ key: 'change', label: 'Change %', align: 'right', sortable: true, editable: true }
];
const positions = [
{ ticker: 'ARC', name: 'Arclight Systems', sector: 'Technology', price: 142.5, change: 2.4 },
{ ticker: 'LUM', name: 'Lumen Dynamics', sector: 'Energy', price: 87.2, change: -1.1 },
{ ticker: 'NVA', name: 'Nova Materials', sector: 'Industrials', price: 56.8, change: 0.7 }
];
export function PositionsGrid() {
return (
<DataGrid
columns={columns}
rows={positions}
selectable
onArcCellChange={(e) => console.log('edited', e.detail)}
onArcSort={(e) => console.log('sort', e.detail.sort)}
onArcSelectionChange={(e) => console.log('selected', e.detail.selectedIndices)}
/>
);
} <script setup>
import { DataGrid } from '@arclux/arc-ui-vue';
const columns = [
{ key: 'ticker', label: 'Ticker', width: '90px', pinned: true, sortable: true },
{ key: 'name', label: 'Company', width: '180px', sortable: true },
{ key: 'sector', label: 'Sector', sortable: true },
{ key: 'price', label: 'Price', align: 'right', sortable: true, editable: true },
{ key: 'change', label: 'Change %', align: 'right', sortable: true, editable: true }
];
const positions = [
{ ticker: 'ARC', name: 'Arclight Systems', sector: 'Technology', price: 142.5, change: 2.4 },
{ ticker: 'LUM', name: 'Lumen Dynamics', sector: 'Energy', price: 87.2, change: -1.1 },
{ ticker: 'NVA', name: 'Nova Materials', sector: 'Industrials', price: 56.8, change: 0.7 }
];
</script>
<template>
<DataGrid
:columns="columns"
:rows="positions"
selectable
@arc-cell-change="(e) => console.log('edited', e.detail)"
@arc-sort="(e) => console.log('sort', e.detail.sort)"
/>
</template> <script>
import { DataGrid } from '@arclux/arc-ui-svelte';
const columns = [
{ key: 'ticker', label: 'Ticker', width: '90px', pinned: true, sortable: true },
{ key: 'name', label: 'Company', width: '180px', sortable: true },
{ key: 'sector', label: 'Sector', sortable: true },
{ key: 'price', label: 'Price', align: 'right', sortable: true, editable: true },
{ key: 'change', label: 'Change %', align: 'right', sortable: true, editable: true }
];
const positions = [
{ ticker: 'ARC', name: 'Arclight Systems', sector: 'Technology', price: 142.5, change: 2.4 },
{ ticker: 'LUM', name: 'Lumen Dynamics', sector: 'Energy', price: 87.2, change: -1.1 },
{ ticker: 'NVA', name: 'Nova Materials', sector: 'Industrials', price: 56.8, change: 0.7 }
];
</script>
<DataGrid
{columns}
rows={positions}
selectable
on:arc-cell-change={(e) => console.log('edited', e.detail)}
on:arc-sort={(e) => console.log('sort', e.detail.sort)}
/> import { Component } from '@angular/core';
import { DataGrid } from '@arclux/arc-ui-angular';
@Component({
imports: [DataGrid],
template: `
<arc-data-grid
[columns]="columns"
[rows]="positions"
selectable
(arcCellChange)="onCellChange($event)"
(arcSort)="onSort($event)"
></arc-data-grid>
`,
})
export class PositionsGridComponent {
columns = [
{ key: 'ticker', label: 'Ticker', width: '90px', pinned: true, sortable: true },
{ key: 'name', label: 'Company', width: '180px', sortable: true },
{ key: 'sector', label: 'Sector', sortable: true },
{ key: 'price', label: 'Price', align: 'right', sortable: true, editable: true },
{ key: 'change', label: 'Change %', align: 'right', sortable: true, editable: true }
];
positions = [
{ ticker: 'ARC', name: 'Arclight Systems', sector: 'Technology', price: 142.5, change: 2.4 },
{ ticker: 'LUM', name: 'Lumen Dynamics', sector: 'Energy', price: 87.2, change: -1.1 },
{ ticker: 'NVA', name: 'Nova Materials', sector: 'Industrials', price: 56.8, change: 0.7 }
];
onCellChange(e: CustomEvent) {
console.log('edited', e.detail);
}
onSort(e: CustomEvent) {
console.log('sort', e.detail.sort);
}
} API
-
columnsArray<{key:string,label:string,sortable?:boolean,editable?:boolean,pinned?:boolean,width?:string,align?:string}>[] - Column definitions. Each entry maps a `key` in your row objects to a rendered column with a `label` header. Optional flags enable sorting, inline editing, and inline-start-edge pinning per column; `width` sets a fixed CSS width (required for accurate pinned offsets) and `align` controls text alignment. Pinned columns are always displayed first. Set via JavaScript property.
-
rowsArray<Record<string, any>>[] - The data array. Each object becomes a row keyed by column `key`. The grid works on an internal shallow copy — sorting and inline edits never mutate the array you pass in. Set via JavaScript property; reassigning it resets selection and any open editor.
-
sortArray<{key:string,direction:'asc'|'desc'}>[] - Multi-sort state in priority order. Clicking a sortable header cycles it asc → desc → none; Shift+click appends it as a secondary sort. When more than one sort is active, headers show a direction arrow plus priority number. Set this property to pre-sort the grid.
-
manual-sortbooleanfalse - Skips internal sorting. Rows render in the order given, while headers still cycle the `sort` state and emit `arc-sort` — use this to implement server-side sorting.
-
selectablebooleanfalse - Adds a checkbox column with a select-all header checkbox (indeterminate when partially selected). Space toggles selection from the keyboard. Emits `arc-select` with the selected row indices.
-
virtualbooleanfalse - Enables virtual scrolling for large datasets. Only visible rows plus an overscan buffer are rendered, keeping performance constant regardless of row count.
-
row-heightnumber40 - Height in pixels of each row when virtual scrolling is enabled. Must match the actual rendered row height for correct scroll calculations.
Events
-
arc-sort - Fired when the user changes sorting. detail: { sort } with the full multi-sort array in priority order
-
arc-select - Fired when row selection changes. detail: { value, selectedIndices } — sorted indices into the original rows array
-
arc-cell-change - Fired when an inline cell edit is committed. detail: { rowIndex, key, value, row } — rowIndex refers to the original rows array
See Also
- Data Table A data-driven table component that renders rows from a JavaScript array. Declarative column definitions via `arc-column` children control which fields appear, their headers, widths, and sort behavior. Built-in support for column sorting, row selection with checkboxes, and an empty-state fallback.
- Table Data-driven table with striped and compact-density variants, powered by columns and rows props.
- Pagination Page navigation control with previous/next arrows, numbered page buttons, and smart ellipsis truncation.