import DataGrid from '@cronocode/react-box/components/dataGrid';role="grid" of rowgroups, rows and gridcells, and it tells assistive technology the size of the whole grid rather than the size of the window it renders: aria-rowcount counts every row, aria-rowindex numbers each one where it really is, and a jump to a row nobody has scrolled to brings it into view first. You write none of that.def.title and the grid points aria-labelledby at it.role="separator" — APG’s window splitter — with the column’s width in pixels on aria-valuenow, so a screen reader reads the new width out as it changes.| Key | What happens |
|---|---|
Tab | Enters the grid at one cell — not at every cell. Shift+Tab leaves it. |
→ / ← | One cell along the row. At either end, focus stays where it is. |
↓ / ↑ | One row, keeping the column — through a group row or a detail panel with fewer cells, and through a grouped header whose cells cover several columns each. |
Home / End | The first or last cell of the row. |
Ctrl + Home / End | The first or last cell of the whole grid, scrolling there if it has not been rendered yet. |
PageDown / PageUp | A screenful of rows at a time. |
Enter / Space | Sorts, on a sortable column header. Anywhere else, steps into the cell’s own control. |
F2 | Steps into the cell’s control even on a header, where Enter is spoken for by the sort. |
Escape | Hands the keyboard back from that control to the cell. |
| Key | What happens |
|---|---|
Tab / F2 | Reaches the resizer of the header cell focus is on. Escape hands the keyboard back to the cell. |
→ / ← | Moves the separator 16px, which widens or narrows the column exactly as dragging it would. |
Home / End | The narrowest the grid allows, or as wide as the grid itself. |
const filters = useMemo(() => {
const result = [];
if (genderFilter) result.push((row) => row.gender === genderFilter);
if (ageFilter === 'under30') result.push((row) => row.age < 30);
return result;
}, [genderFilter, ageFilter]);
<DataGrid
data={allData}
filters={filters}
def={{
title: 'All Features Demo',
topBar: true,
bottomBar: true,
globalFilter: true,
topBarContent: <CustomTopBarFilter ... />,
rowSelection: { pinned: true },
showRowNumber: { pinned: true },
rowHeight: 40,
visibleRowsCount: 8,
columns: [...],
}}
/><DataGrid
data={data}
def={{
columns: [
{ key: 'first_name', header: 'First name' },
{ key: 'last_name', header: 'Last name' },
{
key: 'age',
header: 'Age',
width: 90,
align: 'right',
Cell: ({ cell }) => {
// better to define this function outside to avoid re-creation on each render
return (
<Flex
bgColor="violet-50"
height="fit"
width="fit"
ai="center"
jc="center"
overflow="hidden"
className="parent"
theme={{ dark: { bgColor: 'violet-600' } }}
>
<Box
px={4}
textOverflow="ellipsis"
overflow="hidden"
textWrap="nowrap"
color="violet-700"
fontWeight={600}
hoverGroup={{ parent: { rotate: 180 } }}
theme={{ dark: { color: 'violet-300' } }}
>
{cell.row.data.age}
</Box>
</Flex>
);
},
},
{ key: 'email', header: 'Email', width: 300 },
{ key: 'street_address' },
{ key: 'city' },
{ key: 'country' },
{ key: 'favorite_color' },
{ key: 'gender' },
{ key: 'ssn' },
{ key: 'birthdate' },
{ key: 'phone_number' },
{ key: 'username' },
{ key: 'credit_card_number' },
{ key: 'salary' },
{ key: 'company_name' },
{ key: 'language' },
{ key: 'currency_code' },
],
rowHeight: 40,
visibleRowsCount: 5,
}}
/><DataGrid
data={data}
def={{
columns: [
{ key: 'first_name', header: 'First name', filterable: true },
{ key: 'last_name', header: 'Last name', filterable: true },
{ key: 'age', header: 'Age', width: 120, align: 'right', filterable: { type: 'number' } },
{ key: 'email', header: 'Email', width: 300, filterable: true },
{ key: 'country', filterable: { type: 'multiselect' } },
{ key: 'gender', filterable: { type: 'multiselect' } },
],
rowHeight: 40,
visibleRowsCount: 8,
topBar: true,
bottomBar: true,
globalFilter: true,
}}
/><DataGrid
data={data}
def={{
columns: [
{
key: 'person',
header: 'Person',
columns: [
{ key: 'first_name', header: 'First name' },
{ key: 'last_name', header: 'Last name' },
],
},
{
key: 'contact',
header: 'Contact',
columns: [
{ key: 'email', header: 'Email', width: 300 },
{ key: 'phone_number', header: 'Phone' },
],
},
{ key: 'country' },
{ key: 'city' },
],
rowSelection: { pinned: true },
showRowNumber: { pinned: true },
}}
/>// Define custom component style trees that extend 'datagrid' — 'orders-datagrid' for the outer
// grid and 'subgrid' for the one inside the detail row. Both names need the same .d.ts
// augmentation every Box.components() entry does; the Theme Setup page shows it.
// isExpanded/isExpandedFirstLeaf/isExpandedLastLeaf variants let you style
// the expanded row's cells individually (e.g. top + side borders).
// detailRow.content gets left/right borders without causing scroll overflow.
Box.components({
subgrid: { extends: 'datagrid', styles: { b: 0, borderRadius: 0, shadow: 'none' } },
'orders-datagrid': {
extends: 'datagrid',
children: {
body: {
children: {
cell: {
variants: {
isExpanded: { bt: 3, bb: 0, bgColor: 'indigo-50', borderColor: 'indigo-300' },
isExpandedFirstLeaf: { bl: 3 },
isExpandedLastLeaf: { br: 3 },
},
},
detailRow: {
styles: { bb: 3, bt: 0, bgColor: 'indigo-50', borderColor: 'indigo-300' },
children: {
content: { styles: { bl: 3, br: 3, borderColor: 'indigo-300' } },
},
},
},
},
},
},
});
<DataGrid
component="orders-datagrid"
data={orders}
def={{
rowKey: 'orderId',
topBar: true,
bottomBar: true,
title: 'Orders',
columns: [
{ key: 'orderId', header: 'Order #', width: 100, flexible: false },
{ key: 'customer', header: 'Customer' },
{ key: 'date', header: 'Date', width: 120 },
{ key: 'status', header: 'Status', width: 120 },
{ key: 'total', header: 'Total', width: 100, align: 'right' },
],
rowDetail: {
content: (order) => (
<DataGrid
component="subgrid"
data={order.items}
def={{
columns: [
{ key: 'product', header: 'Product' },
{ key: 'qty', header: 'Qty', width: 80, align: 'right' },
{ key: 'price', header: 'Price', width: 100, align: 'right' },
],
visibleRowsCount: 'all',
rowHeight: 36,
}}
/>
),
pinned: true,
expandOnRowClick: true,
},
}}
/>const [data, setData] = useState([]);
const [page, setPage] = useState(1);
const [totalCount, setTotalCount] = useState(0);
const pageSize = 8;
// onServerStateChange fires on every page/sort/filter change.
// state = { page, pageSize, sortColumn, sortDirection, globalFilterValue, columnFilters }
// columnFilters example: { first_name: { type: 'text', value: 'Jo' }, age: { type: 'number', operator: 'gte', value: 30 }, country: { type: 'multiselect', values: ['Brazil'] } }
const fetchData = useCallback((state) => {
setLoading(true);
api.getUsers({
Page: state.page, PageSize: state.pageSize,
SortBy: state.sortColumn, SortDir: state.sortDirection,
Search: state.globalFilterValue,
Filters: state.columnFilters,
}).then((res) => {
setData(res.Items);
setTotalCount(res.TotalCount);
setPage(res.Page);
setLoading(false);
});
}, []);
useEffect(() => { fetchData({ page: 1, pageSize }); }, []);
<DataGrid
data={data}
loading={loading}
page={page}
onServerStateChange={fetchData}
def={{
columns: [
{ key: 'first_name', header: 'First Name', filterable: true },
{ key: 'age', header: 'Age', filterable: { type: 'number', placeholder: 'Min age' } },
// In server-side mode the grid only has the current page, so provide
// all possible options explicitly (fetch them from your API once).
{ key: 'country', filterable: { type: 'multiselect', options: countryOptions } },
...
],
globalFilter: true,
visibleRowsCount: pageSize,
topBar: true, bottomBar: true,
title: 'Server-Side Pagination & Filters',
pagination: { totalCount },
}}
/><DataGrid
data={data}
def={{
columns: [
{ key: 'first_name', header: 'First name' },
{ key: 'last_name', header: 'Last name' },
{ key: 'age', header: 'Age', width: 100, sortable: true }, // Override: sortable
{ key: 'email', header: 'Email', width: 300, resizable: true }, // Override: resizable
{ key: 'country' },
{ key: 'city' },
],
rowHeight: 40,
visibleRowsCount: 5,
sortable: false, // Disable sorting globally
resizable: false, // Disable resizing globally
}}
/>// 'hover' — resizer appears only when hovering the header cell
<DataGrid
data={data}
def={{
columns: [
{ key: 'first_name', header: 'First name' },
{ key: 'last_name', header: 'Last name' },
{ key: 'age', header: 'Age', width: 100 },
{ key: 'email', header: 'Email', width: 300 },
{ key: 'country' },
{ key: 'city' },
],
rowHeight: 40,
visibleRowsCount: 5,
resizerStyle: 'hover', // 'visible' | 'hover' | 'hidden'
}}
/>// 'smooth' (default): width updates batched to one per animation frame (~60fps, ~1 frame
// behind the cursor). 'instant': width updates synchronously on every pointer move, so the
// column tracks the cursor with no added latency. Drag a column edge and toggle to compare.
<DataGrid
data={data}
def={{
columns: [
{ key: 'first_name', header: 'First name' },
{ key: 'last_name', header: 'Last name' },
{ key: 'age', header: 'Age', width: 100 },
{ key: 'email', header: 'Email', width: 300 },
{ key: 'country' },
{ key: 'city' },
],
rowHeight: 40,
visibleRowsCount: 5,
resizeMode: 'smooth', // or 'instant'
}}
/>