Table is build with a combination of Chakra UI Table and Tanstack/React-Table.
You can control width, position and much more by using style props on the TableToolbar and TableContent components.
To change properties of a certain column like the width you do so by adding the size property to the column array. See Column Defs for more information about the columns.
To manage large chunks of data you can use it together with the Paginator component.
Table subcomponents
Section titled “Table subcomponents”-
TableThe wrapper component that provides context to the subcomponents. -
TableToolbarThe toolbar of the table. A composable component that can contain:TableToolbarSearch- Search/filter inputTableToolbarSettings- Column visibility, rows per page and breaking content settingsTableToolbarActions- Custom action menuTableToolbarAddButton- Add new item button
-
TableContentHouses the rows and columns of the table, has functionality such as row click interaction, break content of certain columns and more.
Base example
Section titled “Base example”import { Badge, Box, Table, TableContent, type ColumnDef, type TableRow } from "@caspeco/casper-ui-library";
type Row = TableRow<{ name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string; description: string;}>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "category", header: "Category" }, { accessorKey: "state", cell: (info) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, }, { accessorKey: "description", header: "Description", size: 200 },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks", description: "Lorem ipsum dolor sit amet.", }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", }, { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", },];
export default function Demo() { return ( <Box w="400px" overflow="auto"> <Table data={data} columns={columns}> <TableContent /> </Table> </Box> );}Sticky headers example
Section titled “Sticky headers example”import { Badge, Box, Table, TableContent, type ColumnDef, type TableRow } from "@caspeco/casper-ui-library";
type Row = TableRow<{ index: number; name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string; description: string;}>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "index", header: "Index" }, { accessorKey: "name", header: "Name" }, { accessorKey: "category", header: "Category" }, { accessorKey: "state", cell: (info) => { if (info.getValue() === "IN_STOCK") { return <Badge.Success>In stock</Badge.Success>; } return <Badge.Error>Out of stock</Badge.Error>; }, header: "Status", size: 100, }, { accessorKey: "description", header: "Description", size: 200 },];
const data: Row[] = Array.from({ length: 30 }, (_, i) => { const state = i % 2 === 0 ? "IN_STOCK" : "OUT_OF_STOCK"; const category = i % 3 === 0 ? "Coffee drinks" : i % 2 === 0 ? "Bottled cold drink" : "Hot sandwiches";
return { index: i, name: `Item ${i + 1}`, state: state, category: category, description: "Lorem ipsum dolor sit amet.", };});
export default function Demo() { return ( <Box w="400px" h="500px" overflow="auto"> <Table data={data} columns={columns}> <TableContent stickyHeaders /> </Table> </Box> );}To get the correct types for your table, you can use the TableRow utility type to define your own row and column types.
This type also adds type safety for built-in features such as nested rows and click handlers.
You may also want to set your row type on Table subcomponents such as <TableContent> to ensure that props such as onClickRow receive the correct type.
import { Table, TableContent, TableRow, ColumnDef } from "@caspeco/casper-ui-library";
type PersonRow = TableRow<{ id: string; name: string }>;
const columns: ColumnDef<PersonRow, unknown>[] = [ { header: "ID", accessorKey: "id" }, { header: "Name", accessorKey: "name" },];
const people: PersonRow[] = [ { id: "1", name: "Alice" },
{ id: "2", name: "Bob", subRows: [{ id: "3", name: "Charlie" }] },];
function MyTable() { return ( <Table data={people} columns={columns}>
<TableContent<PersonRow> onClickRow={(person) => doSomething(person.name)} /> </Table> );}Row ID
Section titled “Row ID”By default, each row is assigned a unique ID based on its index in the data array (nested rows join together with . using their grandparents’ index e.g. "0.1.0").
This ID is used internally by the table to manage row selection, expansion, and other features.
Custom ID
Section titled “Custom ID”If each entry in your table data already have unique IDs (for example, GUIDs from the database) you can use the getRowId prop on the Table component to specify a function that overrides the default ID for each row.
This is important when using features like pagination and row selection to ensure that the correct rows are identified.
Note that this also overrides the IDs to provide when using props such as initialExpandedState, initialBreakingContentState, etc.
import { Table, TableContent, ToggleButton, type ColumnDef, type TableRow } from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ id: string; name: string }>;
const columns: ColumnDef<Row, unknown>[] = [ { header: "ID", cell: (cellContext) => cellContext.row.id }, // Always render row ID { accessorKey: "name", header: "Name" },];
const data = [ { id: "abc", // default ID: "0" name: "John Doe", }, { id: "123", // default ID: "1" name: "Jane Smith", subRows: [ { id: "456", // default ID: "1.0" name: "Jim Brown", }, { id: "789", // default ID: "1.1" name: "Jake White", subRows: [ { id: "xyz", // default ID: "1.1.0" name: "Jill Green", }, ], }, ], },];
export default function Demo() { const [isCustomId, setIsCustomId] = useState(false);
function getRowId(row: Row) { return row.id; }
return ( <> <ToggleButton onClick={() => setIsCustomId((prev) => !prev)} isPressed={isCustomId}> Use custom ID </ToggleButton> <Table key={isCustomId ? "custom" : "default"} // force re-mount needed in this example, normally getRowId should not change! data={data} getRowId={isCustomId ? getRowId : undefined} columns={columns} initialExpandedState={true} > <TableContent /> </Table> </> );}Row interaction
Section titled “Row interaction”Use onClickRow on the TableContent component to provide row interaction.
import { Table, TableContent, type ColumnDef, type TableRow } from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ id: string; name: string }>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Namn" }, { accessorKey: "id", header: "Id" },];
const data: Row[] = [ { name: "Row one", id: "1" }, { name: "Row two", id: "2" },];
export default function Demo() { const [clickedRow, setClickedRow] = useState<Row | undefined>();
return ( <> <Table data={data} columns={columns}> <TableContent<Row> onClickRow={setClickedRow} /> </Table> {clickedRow && <p>Clicked: {clickedRow.name}</p>} </> );}Table toolbar
Section titled “Table toolbar”Add the TableToolbar component inside Table to see a toolbar over your content. TableToolbar is a composable component with the following subcomponents:
TableToolbarSearch- Search input with iconTableToolbarSettings- Settings menu for column visibility, rows per page, and breaking content. By default, the rows-per-page selector is hidden, but it can be shown by providingpageSizeandonPageSizeChangeprops.TableToolbarActions- Custom action menu (acceptsMenuListas children)TableToolbarAddButton- Add button with icon
The TableToolbar accepts an optional title prop to display a table name. Subcomponents can be used in any combination.
import { DownIcon, MenuItem, MenuList, Table, TableContent, TableToolbar, TableToolbarActions, TableToolbarAddButton, TableToolbarSearch, TableToolbarSettings, type ColumnDef,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = { name: string; type: string; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "type", header: "Type" }, { accessorKey: "category", header: "Category" },];
export default function Demo() { const [data, setData] = useState<Row[]>([ { name: "Americano", type: "Drinks", category: "Espresso drink" }, { name: "Orange juice", type: "Drinks", category: "Bottled cold drink" }, { name: "Bagel", type: "Food", category: "Hot sandwiches" }, ]);
const [displayedData, setDisplayedData] = useState(data);
const onSearch = (event: React.ChangeEvent<HTMLInputElement>) => { const searchTerm = event.target.value.toLowerCase(); const filteredItems = data.filter((value) => value.name.toLowerCase().includes(searchTerm));
return setDisplayedData(filteredItems); };
const onAddNewItem = () => { const newItem = { name: "Toast", type: "Food", category: "Hot sandwiches" }; setData((prevValue) => [newItem, ...prevValue]); setDisplayedData((prevValue) => [newItem, ...prevValue]); };
const removeLatest = () => { data.shift(); setData([...data]); setDisplayedData([...data]); };
return ( <Table data={displayedData} columns={columns}> <TableToolbar title="Articles"> <TableToolbarSearch onChange={onSearch} /> <TableToolbarSettings /> <TableToolbarActions> <MenuList> <MenuItem value="remove" leftIcon={DownIcon} onClick={removeLatest}> Remove latest </MenuItem> </MenuList> </TableToolbarActions> <TableToolbarAddButton onClick={onAddNewItem} /> </TableToolbar> <TableContent /> </Table> );}Column visibility (and how to store it)
Section titled “Column visibility (and how to store it)”When using the TableToolbarSettings component it is possible to control the visibility of the columns in the table.
Click the settings icon in the toolbar to open the column visibility filter.
To control the column visibility states you can use the initialColumnVisibilityState and onColumnVisibilityChange properties on the Table component!
import { Badge, Table, TableContent, TableToolbar, TableToolbarSettings, type ColumnDef, type VisibilityState,} from "@caspeco/casper-ui-library";
type Row = { name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "category", header: "Category" }, { accessorKey: "state", cell: (info) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const initialColumnVisibilityState = window.sessionStorage.getItem("column-visibility-state");
const onColumnVisibilityChange = (state: VisibilityState) => { window.sessionStorage.setItem("column-visibility-state", JSON.stringify(state)); };
return ( <Table data={data} initialColumnVisibilityState={ initialColumnVisibilityState ? (JSON.parse(initialColumnVisibilityState) as VisibilityState) : undefined } onColumnVisibilityChange={onColumnVisibilityChange} columns={columns} > <TableToolbar title="Articles"> <TableToolbarSettings /> </TableToolbar> <TableContent /> </Table> );}Handle column visibility from parent component
Section titled “Handle column visibility from parent component”You can change the visibility of a column dynamically by passing down an object in columnVisibilityState that describes the visibility states of the columns, by matching the id of the column. For example based on a value in the parent component.
import { Badge, Button, Table, TableContent, type CellContext, type ColumnDef,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = { name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const [shouldShowStateColumn, setShouldShowStateColumn] = useState(true); const [columnVisibilityState, setColumnVisibilityState] = useState({ name: true, category: true, state: shouldShowStateColumn, });
const handleOnChangeColumnVisibility = () => { setShouldShowStateColumn(!shouldShowStateColumn); if (shouldShowStateColumn) { setColumnVisibilityState({ ...columnVisibilityState, state: false }); } else { setColumnVisibilityState({ ...columnVisibilityState, state: true }); } };
return ( <> <Button onClick={handleOnChangeColumnVisibility}>Toggle state column visibility</Button> <Table data={data} columns={columns} columnVisibilityState={columnVisibilityState}> <TableContent /> </Table> </> );}Nested data
Section titled “Nested data”To render hierarchical or nested data within a table, structure your data objects to include a subRows property.
Each data item can contain a subRows array, which holds its nested items. The table component will automatically
handle and display this nested data, adding an expand/collapse button in the first column.
import { Table, TableContent, type TableRow } from "@caspeco/casper-ui-library";
type Row = TableRow<{ name: string; price: number }>;
const columns = [ { accessorKey: "name", header: "Name", size: 320 }, { accessorKey: "price", header: "Price", size: 100 },];
const data: Row[] = [ { name: "Item 1", price: 100.0, subRows: [ { name: "Item 1.1", price: 110.0, subRows: [ { name: "Item 1.1.1", price: 111.0 }, { name: "Item 1.1.2", price: 112.0, subRows: [ { name: "Item 1.1.2.1", price: 112.1 }, { name: "Item 1.1.2.2", price: 112.2 }, ], }, ], }, ], },];
export default function Demo() { return ( <Table data={data} columns={columns}> <TableContent /> </Table> );}Persisting expanded state
Section titled “Persisting expanded state”To maintain the expand/collapse state across page reloads or sessions, use the initialExpandedState and onExpandedChange props. This allows you to save the state externally (e.g., in sessionStorage) and reapply it when the component mounts.
In this example, the expand/collapse state is stored in sessionStorage.
The initialExpandedState prop initializes the table with the stored state, and onExpandedChange updates the storage whenever the state changes.
import { Table, TableContent, type ExpandedState, type TableRow } from "@caspeco/casper-ui-library";
type Row = TableRow<{ name: string; price: number }>;
const columns = [ { accessorKey: "name", header: "Name", size: 320 }, { accessorKey: "price", header: "Price", size: 100 },];
const data: Row[] = [ { name: "Item 1", price: 100.0, subRows: [ { name: "Item 1.1", price: 110.0, subRows: [ { name: "Item 1.1.1", price: 111.0 }, { name: "Item 1.1.2", price: 13.0, subRows: [ { name: "Item 1.1.1.1", price: 111.0 }, { name: "Item 1.1.1.2", price: 111.0 }, ], }, ], }, ], },];
export default function Demo() { const initiallyExpandedState = window.sessionStorage.getItem("expanded-table-docs-example");
const onExpandedChange = (state: ExpandedState) => { window.sessionStorage.setItem("expanded-table-docs-example", JSON.stringify(state)); };
return ( <> <Table data={data} initialExpandedState={ initiallyExpandedState ? (JSON.parse(initiallyExpandedState) as ExpandedState) : {} } onExpandedChange={onExpandedChange} columns={columns} > <TableContent /> </Table> <p>Reload the page to see that the expanded/collapsed state of the rows is preserved!</p> </> );}Advanced control over expanded state
Section titled “Advanced control over expanded state”For finer control over the expanded state, you can use the expandedState prop to manage it externally.
This makes the table’s expand/collapse functionality fully controlled by your component, similar to how controlled inputs work in React.
When using expandedState, you must provide an onExpandedChange handler to update the state based on user interactions.
In this example, the expandedState is set to true when the user types into the search field, causing all rows to expand.
import { Table, TableContent, TableToolbar, TableToolbarSearch, type ColumnDef, type TableRow,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ name: string; price: number }>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name", size: 320 }, { accessorKey: "price", header: "Price", size: 100 },];
const data: Row[] = [ { name: "Item 1", price: 100.0, subRows: [ { name: "Item 1.1", price: 110.0, subRows: [ { name: "Item 1.1.1", price: 111.0 }, { name: "Item 1.1.2", price: 13.0, subRows: [ { name: "Item 1.1.1.1", price: 111.0 }, { name: "Item 1.1.1.2", price: 111.0 }, ], }, ], }, ], },];
export default function Demo() { const [expandedState, setExpandedState] = useState({});
return ( <Table data={data} expandedState={expandedState} onExpandedChange={setExpandedState} columns={columns}> <TableToolbar title="Articles"> <TableToolbarSearch onChange={(e) => { if (e.target.value.length > 0) { // Forcibly expand all rows when searching setExpandedState(true); } }} /> </TableToolbar> <TableContent /> </Table> );}Row actions
Section titled “Row actions”You can add a list of actions to each row by providing a getRowActions to <Table>.
import { Box, Button, DeleteIcon, MenuItem, MenuList, showToast, Table, TableContent, ThemeSpace, Toaster, TrendUpIcon, type ColumnDef, type TableRow,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ name: string; netWorth: number }>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "netWorth", header: "Net Worth ($B)" },];
const defaultData: Row[] = [ { name: "Elon Musk", netWorth: 476.3 }, { name: "Larry Ellison", netWorth: 362.7 }, { name: "Mark Zuckerberg", netWorth: 261.5 }, { name: "Jeff Bezos", netWorth: 244.7 },];
export default function Demo() { const [data, setData] = useState(defaultData);
function getRowActions(row: Row) { return ( <MenuList> <MenuItem leftIcon={TrendUpIcon} onClick={() => promote(row)} value="promote"> Promote </MenuItem> <MenuItem leftIcon={DeleteIcon} onClick={() => fire(row)} value="fire"> Fire </MenuItem> </MenuList> ); }
function promote(row: Row) { const promotion = Math.round(Math.random() * 100); setData((prev) => prev.map((r) => { if (r.name === row.name) { return { ...r, netWorth: r.netWorth + promotion }; } return r; }), ); showToast({ description: `Promoted ${row.name} by $${promotion}B`, type: "success" }); }
function fire(row: Row) { setData((prev) => prev.filter((r) => r.name !== row.name)); showToast({ description: `Fired ${row.name}`, type: "error" }); }
return ( <Box w="400px" height="500px" pl={ThemeSpace.Medium}> <Button mb={ThemeSpace.Medium} onClick={() => setData(defaultData)}> Reset </Button>
<Table data={data} columns={columns} initialSortingState={[{ id: "netWorth", desc: true }]} getRowActions={getRowActions} > <TableContent /> </Table>
<Toaster /> </Box> );}Breaking content (and how to store it)
Section titled “Breaking content (and how to store it)”In the TableToolbarSettings component and in each column header it is possible to set a breaking content setting. This will determine if the content should break into multiple lines or not.
To control the breaking content state you can use the initialBreakingContentState and onBreakingContentChange properties on the Table component!
import { Table, TableContent, TableToolbar, TableToolbarSettings, type ColumnDef, type TableRow,} from "@caspeco/casper-ui-library";
type Row = TableRow<{ name: string; description: string }>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name" }, { accessorKey: "description", header: "Description" },];
const data: Row[] = [ { name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus vel nunc.", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus vel nunc.", }, { name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus.", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus.", },];
export default function Demo() { const initialBreakingContentState = window.sessionStorage.getItem("breaking-content-state");
const onBreakingContentChange = (state: Record<string, boolean>) => { window.sessionStorage.setItem("breaking-content-state", JSON.stringify(state)); };
return ( <Table data={data} initialBreakingContentState={ initialBreakingContentState ? (JSON.parse(initialBreakingContentState) as Record<string, boolean>) : undefined } onBreakingContentChange={onBreakingContentChange} columns={columns} > <TableToolbar title="Articles"> <TableToolbarSettings /> </TableToolbar> <TableContent /> </Table> );}Pagination
Section titled “Pagination”Table can be used with the Paginator component. To use it you need to control the data that is shown in the table and the page you are on. The Paginator component has the following required properties:
pageCountis the total number of pages in the tablepageIndexis the current page you are onpageSizeis the number of items shown per pagesetPageIndexfunction that sets the current pagesetPageSizefunction that sets the number of items shown per pagecanPreviousPageboolean that should determine if it should be possible to go to the previous pagecanNextPageboolean that should determine if it should be possible to go to the next page
You can use it inside or outside the table component.
The page size selector is available in the table toolbar through the TableToolbarSettings component. It works with the Paginator component. To use it, provide the pageSize and onPageSizeChange props to TableToolbarSettings. It is hidden by default if either of these props is not provided.
import { Paginator, Table, TableContent, TableToolbar, TableToolbarSettings, type ColumnDef,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = { name: string; price: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", cell: ({ getValue }) => getValue(), header: "Name" }, { accessorKey: "price", cell: ({ getValue }) => getValue(), header: "Price" },];
const data: Row[] = Array(60) .fill("") .map((_, i) => ({ name: `Item ${i + 1}`, price: "10.00", }));
export default function Demo() { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10);
const paginatedData = data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
return ( <> <Table data={paginatedData} columns={columns}> <TableToolbar> <TableToolbarSettings pageSize={pageSize} onPageSizeChange={setPageSize} /> </TableToolbar> <TableContent /> </Table> <Paginator pageSize={pageSize} itemCount={data.length} onPageChange={setPageIndex} /> </> );}Inline editing
Section titled “Inline editing”To enable inline editing in the table you need to three utility functions.
- A custom input component that handles the local state of the input
- A function that updates the data of the table on blur (we don’t want this to happen on every keystroke)
- A column definition that calls the render function and passes the cell info to it
A full live example is shown below:
import { Input, Table, TableContent, ThemeSpace, type CellContext, type ColumnDef,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = { name: string; age: number };
export default function Demo() { const columns: ColumnDef<Row>[] = [ { accessorKey: "name", header: "Name", size: 200, cell: (cellInfo) => <EditableField cellInfo={cellInfo} type="text" updateData={updateData} />, }, { accessorKey: "age", header: "Age", size: 100, cell: (cellInfo) => <EditableField cellInfo={cellInfo} type="number" updateData={updateData} />, }, ];
const [data, setData] = useState([ { name: "Robyn", age: 31 }, { name: "Mustafa", age: 28 }, { name: "Alice", age: 25 }, ]);
const updateData = (cellInfo: CellContext<Row, unknown>, value: string) => { const updatedData = [...data]; const rowIndex = cellInfo.row.index; const columnId = cellInfo.column.id; const rowToUpdate = updatedData[rowIndex]; const updatedRow = { ...rowToUpdate, [columnId]: value };
updatedData[rowIndex] = updatedRow;
setData(updatedData); };
return ( <Table<Row> data={data} columns={columns}> <TableContent /> </Table> );}
interface EditableFieldProps { cellInfo: CellContext<Row, unknown>; type: "text" | "number"; updateData: (cellInfo: CellContext<Row, unknown>, value: string) => void;}
const EditableField = ({ cellInfo, type, updateData }: EditableFieldProps) => { const initialValue = (cellInfo.getValue() ?? "") as string; const [value, setValue] = useState(initialValue ?? "");
const onBlur = () => { // Update the table data when the input loses focus updateData(cellInfo, value); };
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => { // Update the input value as the user types setValue(event.target.value); };
return ( <Input value={value} type={type} variant="ghost" onChange={onChange} onBlur={onBlur} // Add aira-label for accessibilty, it should be the column name aria-label={cellInfo.column.columnDef.header?.toString()} size="sm" m={ThemeSpace.XSmall} /> );};Custom row indication
Section titled “Custom row indication”You can use the table prop customRowIndication to set your own row as “clicked”. onRowClickIndication prop still works together with customRowIndication but if you want to be able to
change the clicked row indication without having to click a row you can use customRowIndication. customRowIndication is of type RowSelectionState which looks like this:
{rowId: true} you can set it to true or false depending on if you want the row to be clicked or not. Every row has their id in the tr element id property.
Advanced control over sorting state
Section titled “Advanced control over sorting state”For control over the sorting state, you can use the sortingState prop to manage it externally.
When using sortingState, you must provide an onSortingChange handler to update the state based on user interactions.
This can be useful if you want to implement server-side sorting.
To disable sorting on a specific column, set enableSorting: false in the column definition. The sort actions will be hidden from that column’s header menu.
import { Badge, Table, TableContent, type CellContext, type ColumnDef, type SortingState,} from "@caspeco/casper-ui-library";import { useMemo, useState } from "react";
type Row = { name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const [sorting, setSorting] = useState<SortingState>([]);
const onSortingChange = (state: SortingState) => { setSorting(state); };
const sortedData = useMemo(() => { if (sorting.length === 0) return data;
return [...data].sort((a, b) => { const { id, desc } = sorting[0]; const aValue = a[id as keyof Row]; const bValue = b[id as keyof Row];
if (!aValue || !bValue) return 0; if (aValue < bValue) return desc ? 1 : -1; if (aValue > bValue) return desc ? -1 : 1; return 0; }); }, [sorting]);
return ( <Table data={sortedData} columns={columns} onSortingChange={onSortingChange} sortingState={sorting}> <TableContent /> </Table> );}Table filtering
Section titled “Table filtering”You can use the TableFilter component to add filtering capabilities to your table.
The TableFilter component accepts the following props:
initialFilterState- An array of filter box objects that define the available filters/comboboxes for the table.onFilterChange- A callback function that is called when the selected filter items change. Use this to get the selected filter items.onFilterVisibleChange- A callback function that is called when the visibility of a filter changes.
import { Badge, Table, TableContent, TableFilter, type CellContext, type ColumnDef,} from "@caspeco/casper-ui-library";import { useMemo, useState } from "react";
type Row = { name: string; status: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "status", id: "status", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", status: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", status: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", status: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const [filters, setFilters] = useState<TableFilter[]>([]);
const onFilterChange = (filters: TableFilter[]) => { setFilters(filters); };
const filteredData = useMemo(() => { if (filters.length === 0) return data;
return data.filter((item) => { return filters.every((filter) => { if (!filter.selectedItems || filter.selectedItems.length === 0) return true;
const itemValue = filter.filterId === "category" ? item.category : item.status; return filter.selectedItems.some((selected) => selected.value === itemValue); }); }); }, [filters]);
return ( <Table data={filteredData} columns={columns}> <TableFilter initialFilterState={[ { filterId: "category", label: "Category", items: [ { id: "1", value: "Coffee drinks", label: "Coffee drinks" }, { id: "2", value: "Bottled cold drink", label: "Bottled cold drink" }, { id: "3", value: "Hot sandwiches", label: "Hot sandwiches" }, ], isVisible: false, selectedItems: [], }, { filterId: "status", label: "Status", items: [ { id: "1", value: "IN_STOCK", label: "In stock", variant: "success" }, { id: "2", value: "OUT_OF_STOCK", label: "Out of stock", variant: "error" }, ], isVisible: false, selectedItems: [], }, ]} onFilterChange={onFilterChange} /> <TableContent /> </Table> );}Table filter add items onVisibilityChange
Section titled “Table filter add items onVisibilityChange”You can use the onFilterVisibleChange prop to add new filter items when a filter box is opened.
This can be useful if you want to load filter items dynamically when the user sets the filter box to be visible.
import { Badge, Table, TableContent, TableFilter, type CellContext, type ColumnDef, type ComboboxItem,} from "@caspeco/casper-ui-library";import type { TableFilterVisibleChange } from "@caspeco/casper-ui-library/dist/components/table/table-filter";import { useMemo, useState } from "react";
type Row = { name: string; status: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "status", id: "status", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", status: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", status: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", status: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const [filters, setFilters] = useState<TableFilter[]>([]);
const filteredData = useMemo(() => { if (filters.length === 0) return data;
return data.filter((item) => { return filters.every((filter) => { if (!filter.selectedItems || filter.selectedItems.length === 0) return true;
const itemValue = filter.filterId === "category" ? item.category : item.status; return filter.selectedItems.some((selected) => selected.value === itemValue); }); }); }, [filters]);
const getDataFromServer = () => { // Simulate fetching data from a server return [ { id: "1", value: "Coffee drinks", label: "Coffee drinks" }, { id: "2", value: "Bottled cold drink", label: "Bottled cold drink" }, { id: "3", value: "Hot sandwiches", label: "Hot sandwiches" }, ]; };
const onFilterVisibleChange = ( filter: TableFilterVisibleChange, addItems: (items: ComboboxItem[]) => void, ) => { if (filter.filterId === "category" && filter.isVisible && filter.items.length === 0) { const itemsFromServer = getDataFromServer(); addItems(itemsFromServer); } };
return ( <Table data={filteredData} columns={columns}> <TableFilter initialFilterState={[ { filterId: "category", label: "Category", items: [], isVisible: false, selectedItems: [], }, { filterId: "status", label: "Status", items: [ { id: "1", value: "IN_STOCK", label: "In stock", variant: "success" }, { id: "2", value: "OUT_OF_STOCK", label: "Out of stock", variant: "error" }, ], isVisible: true, selectedItems: [], }, ]} onFilterChange={setFilters} onFilterVisibleChange={onFilterVisibleChange} /> <TableContent /> </Table> );}Row checkbox selection
Section titled “Row checkbox selection”You can enable row selection in the table by using the onSelectionChange prop.
This prop receives a callback function that is called whenever the selection state changes. You can use this to update your local state or perform any other actions based on the selected rows.
import { Badge, Table, TableContent, type CellContext, type ColumnDef } from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = { name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink" }, { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches" },];
export default function Demo() { const [selectionState, setSelectionState] = useState<string[]>([]);
return ( <> Items selected: {selectionState.length} <br /> Selected ids: {selectionState.join(", ")} <Table data={data} columns={columns} onSelectionChange={setSelectionState}> <TableContent /> </Table> </> );}Row selection actions
Section titled “Row selection actions”You can define actions that can be performed on the selected rows using the selectionActions prop.
If more than three actions are defined, they will be grouped into a dropdown menu. On smaller screens, all actions will appear in a dropdown regardless of their number.
import { Button, Table, TableContent, ThemeSpace, type ColumnDef, type TableRow,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ id: string; vat: string; account: string }>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "id", id: "id", header: "ID" }, { accessorKey: "vat", id: "vat", header: "VAT" }, { accessorKey: "account", id: "account", header: "Account" },];
type ActionType = "change-vat" | "change-account" | "delete";
const actionTitleMap: Record<ActionType, string> = { "change-vat": "change VAT", "change-account": "change account", delete: "delete",};
const initialData: Row[] = [ { id: "1", vat: "25%", account: "111 - Services" }, { id: "2", vat: "12%", account: "222 - Food" }, { id: "3", vat: "6%", account: "333 - Events" },];
export default function Demo() { const [data, setData] = useState<Row[]>(initialData); const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const resetData = () => { setData(initialData); setSelectedRowIds([]); };
const handleAction = (type: ActionType) => (selectedRowIds: string[]) => { if (type === "delete") { const confirmDelete = confirm(`Are you sure you want to delete ${selectedRowIds.length} rows?`); if (confirmDelete) { setData((prevData) => prevData.filter((row) => !selectedRowIds.includes(row.id))); setSelectedRowIds([]); } return; }
const promptValue = prompt( `Are you sure you want to ${actionTitleMap[type]} for ${selectedRowIds.length} rows?`, );
if (promptValue) { if (type === "change-vat") { setData((prevData) => prevData.map((row) => (selectedRowIds.includes(row.id) ? { ...row, vat: promptValue } : row)), ); } else { setData((prevData) => prevData.map((row) => (selectedRowIds.includes(row.id) ? { ...row, account: promptValue } : row)), ); } } };
return ( <> <Button mb={ThemeSpace.Medium} onClick={resetData}> Reset </Button> <Table data={data} columns={columns} getRowId={(row) => row.id} onSelectionChange={setSelectedRowIds} selectionState={selectedRowIds} selectionActions={[ { name: "Change VAT", onClick: handleAction("change-vat") }, { name: "Change account", onClick: handleAction("change-account") }, { name: "Remove", variant: "delete", onClick: handleAction("delete") }, ]} > <TableContent /> </Table> </> );}Virtualized table
Section titled “Virtualized table”You can enable virtualization in the table by using the virtualization prop on the TableContent component.
This is useful when you have a large number of rows and want to improve performance by only rendering the visible rows.
estimateRowHeight- Estimated row height in pixels for virtualization calculations. Default: 40overscan- Number of items to render outside the visible area for smoother scrolling. Default: 5height- Height of the scroll container. Can be a number (interpreted as pixels) or a string with CSS units (e.g., “600px”, “100vh”). Default: “600px”
import { Badge, Table, TableContent, type CellContext, type ColumnDef } from "@caspeco/casper-ui-library";
type Row = { name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = Array.from({ length: 500 }, (_, i) => ({ name: `Item ${i + 1}`, category: `Category ${i + 1}`, state: i % 2 === 0 ? "IN_STOCK" : "OUT_OF_STOCK",}));
export default function Demo() { return ( <Table data={data} columns={columns}> <TableContent stickyHeaders virtualization={{ height: "300px", }} /> </Table> );}Disable checkbox for specific rows
Section titled “Disable checkbox for specific rows”You can disable the selection checkbox for specific rows by using the disableSelectionCheckbox prop.
This prop accepts a function that receives the row as an argument and should return a boolean indicating whether the checkbox should be disabled for that row.
import { Badge, Table, TableContent, type CellContext, type ColumnDef, type TableRow,} from "@caspeco/casper-ui-library";import { useState } from "react";
type Row = TableRow<{ name: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string; isDisabled?: boolean;}>;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = [ { name: "Americano", state: "IN_STOCK", category: "Coffee drinks" }, { name: "Orange juice", state: "OUT_OF_STOCK", category: "Bottled cold drink", isDisabled: true }, { name: "Bröd", state: "IN_STOCK", category: "Hot sandwiches",
subRows: [ { name: "Bagel", state: "IN_STOCK", category: "Hot sandwiches", subRows: [{ name: "Ost bagel", state: "IN_STOCK", category: "Hot sandwiches", isDisabled: true }], }, ], },];
export default function Demo() { const [selectionState, setSelectionState] = useState<string[]>([]);
return ( <> Items selected: {selectionState.length} <br /> Selected ids: {selectionState.join(", ")} <Table data={data} columns={columns} onSelectionChange={setSelectionState} disableSelectionCheckbox={(row) => row.original.isDisabled ?? false} > <TableContent /> </Table> </> );}Drag and drop columns
Section titled “Drag and drop columns”Enable column reordering with the enableColumnReorder prop on the Table component. Users can then drag
a column header to move it.
Columns can also be moved from the keyboard: focus a header, press Space or Enter to pick the column
up, ← and → to move it, Space or Enter to drop it, and Escape to put it back.
To hold one column in place, set meta.disableColumnReorder to true on its column definition. It stays
where it is and cannot be dragged.
import { Badge, Table, TableContent, type CellContext, type ColumnDef } from "@caspeco/casper-ui-library";
type Row = { name: string; brand: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name", meta: { disableColumnReorder: true } }, { accessorKey: "brand", id: "brand", header: "Brand" }, { accessorKey: "category", id: "category", header: "Category" }, { accessorKey: "state", id: "state", cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 100, },];
const data: Row[] = Array.from({ length: 20 }, (_, i) => ({ name: `Item ${i + 1}`, brand: ["Brand A", "Brand B", "Brand C"][i % 3], category: `Category ${i + 1}`, state: i % 2 === 0 ? "IN_STOCK" : "OUT_OF_STOCK",}));
export default function Demo() { return ( <Table data={data} columns={columns} enableColumnReorder> <TableContent /> </Table> );}Persisting column order (and how to store it)
Section titled “Persisting column order (and how to store it)”To keep the column order across reloads, save it with onColumnOrderChange and hand it back through
initialColumnOrder when the table mounts. Both use the same shape - an array of column IDs - so you can
store what one gives you and pass it straight to the other.
Advanced control over column order
Section titled “Advanced control over column order”To drive the order from your own state, pass it in columnOrderState instead. The table then shows exactly
the order you give it, the way a controlled input shows exactly the value you give it, so you must also
pass onColumnOrderChange and save what it reports - otherwise dragging appears to do nothing.
Use this when you want to set the order from code, or keep two tables in step.
Reordering rows
Section titled “Reordering rows”Enable row reordering with enableRowReorder on the Table component.
data is the source of truth: the table moves the row for you, but any new data replaces the dragged
order. Use onRowOrderChange to save the order and pass it back in through data.
onRowOrderChange receives RowReorderDetails:
rowId— the row that movedafterRowId/beforeRowId— its new neighbours,undefinedat either end.orderedRowIds— the rows fromdata, in their new orderrevert()— puts that row back where the drag found it, leaving earlier drags alone. Call this on every failed save. Reordering while the table is sorted is handled automatically, but you need to manually switch reordering off while the table is filtered, e.g.enableRowReorder={filters.length === 0}. The table can be reordered using both the mouse and the keyboard.Key Does Space or Enter Pick the row up, and drop it again ↑ / ↓ Move the picked-up row Escape Put it back where it started Tab Drop the row and move on import { Box, Table, TableContent, type ColumnDef, type RowReorderDetails } from "@caspeco/casper-ui-library";import { useState } from "react";type Row = { id: string; name: string; category: string };const columns: ColumnDef<Row>[] = [{ accessorKey: "name", id: "name", header: "Name" },{ accessorKey: "category", id: "category", header: "Category" },];const initialData: Row[] = [{ id: "a", name: "Americano", category: "Espresso drink" },{ id: "b", name: "Bagel", category: "Bread" },{ id: "c", name: "Cappuccino", category: "Espresso drink" },{ id: "d", name: "Doughnut", category: "Pastry" },{ id: "e", name: "Espresso", category: "Espresso drink" },];let saveCount = 0;const saveRowOrder = (details: { rowId: string; afterId: string | null; beforeId: string | null }) =>new Promise<void>((resolve, reject) => {saveCount += 1;const failure = new Error(`Could not save the new position of ${details.rowId}`);setTimeout(() => (saveCount % 3 === 0 ? reject(failure) : resolve()), 300);});export default function Demo() {const [data, setData] = useState(initialData);const [lastMove, setLastMove] = useState<string>("Drag a row by its handle to reorder it.");const saveMove = async ({ rowId, afterRowId, beforeRowId, orderedRowIds, revert }: RowReorderDetails) => {try {// The backend is told which rows the moved one now sits between, so it can work out the position// itself. Indexes would only describe this page.await saveRowOrder({ rowId, afterId: afterRowId ?? null, beforeId: beforeRowId ?? null });setLastMove(afterRowId ? `Moved "${rowId}" after "${afterRowId}"` : `Moved "${rowId}" to the top`);setData((current) => {const byId = new Map(current.map((row) => [row.id, row]));return orderedRowIds.flatMap((id) => byId.get(id) ?? []);});} catch {setLastMove(`Could not save the new position of "${rowId}", so it moved back.`);revert();}};const onRowOrderChange = (details: RowReorderDetails) => void saveMove(details);return (<Box w="400px" h="400px"><Tabledata={data}columns={columns}enableRowReordergetRowId={(row) => row.id}onRowOrderChange={onRowOrderChange}><TableContent /></Table><p>{lastMove}</p></Box>);}
Resizing columns
Section titled “Resizing columns”Enable column resizing with the enableColumnResizing prop on the Table component. Users can then drag the right edge of any column header to adjust its width, and double-click the edge to reset a column to its defined width.
While dragging, a resize indicator follows the pointer to preview where the column edge will land; the new width is applied when the drag is released. This keeps resizing smooth even on large tables, since the columns are not recalculated on every frame of the drag.
A column’s default width comes from the size field on its column definition. You can constrain how far a column may be dragged with the native minSize and maxSize fields, and opt a specific column out of resizing entirely with enableResizing: false.
size is the width a column falls back to, so it is the place for a design decision, not for a width the user chose earlier — see persisting column widths for that.
Resizing from the keyboard
Section titled “Resizing from the keyboard”The resize handle is part of the tab order, so a column can be resized without a pointer. Tab to a column header, then once more to reach its handle — a screen reader announces it as Resize {column} column along with the current width, and reads out each new width as it changes.
| Key | Effect |
|---|---|
| ← / → | Narrower or wider, one 10px step |
| Shift + ← / → | The same in 50px steps |
| Home | Shrink to the column’s minimum width |
| End | Grow to the column’s maxSize, if it declares one |
| Enter | Reset to the column’s defined width, like double-clicking the handle |
No resize indicator appears while resizing this way. The line exists to preview where a drag will land, and a keypress applies its width straight away, so there is nothing to preview.
Persisting column widths (and how to store it)
Section titled “Persisting column widths (and how to store it)”To keep column widths across page reloads or sessions, use onColumnSizingChange to save them (e.g. to localStorage) and initialColumnSizingState to hand them back when the table mounts.
onColumnSizingChange fires once per resize gesture (when the drag ends), with a map of column ID to width in pixels — so persistence behaves just like column sorting and visibility. A keyboard resize has no release to wait for, so there it fires once per keypress that changes a width.
That map holds only the columns whose width differs from their size, so it stays small and a column you never touch keeps following its default. Store it as it arrives and pass it straight back; the two props are the round trip.
initialColumnSizingState is read once, when the table mounts. If your stored widths arrive later than the first render — from an API, or storage you read asynchronously — wait for them before rendering TableContent, otherwise the table mounts with nothing seeded.
import { Badge, Box, Table, TableContent, type CellContext, type ColumnDef, type ColumnSizingState,} from "@caspeco/casper-ui-library";
type Row = { name: string; brand: string; state: "IN_STOCK" | "OUT_OF_STOCK"; category: string };
const STORAGE_KEY = "docs-resizable-table-widths";const savedWidths = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "{}") as ColumnSizingState;
const columns: ColumnDef<Row>[] = [ { accessorKey: "name", id: "name", header: "Name", size: 200 }, { accessorKey: "brand", id: "brand", header: "Brand", size: 160 }, { accessorKey: "category", id: "category", header: "Category", size: 160 }, { accessorKey: "state", id: "state", enableResizing: false, cell: (info: CellContext<Row, unknown>) => { if (info.getValue() === "IN_STOCK") { return ( <Badge.Success> <>In stock</> </Badge.Success> ); } return ( <Badge.Error> <>Out of stock</> </Badge.Error> ); }, header: "Status", size: 120, },];
const data: Row[] = Array.from({ length: 20 }, (_, i) => ({ name: `Item ${i + 1}`, brand: ["Brand A", "Brand B", "Brand C"][i % 3], category: `Category ${i + 1}`, state: i % 2 === 0 ? "IN_STOCK" : "OUT_OF_STOCK",}));
export default function Demo() { const onColumnSizingChange = (sizing: ColumnSizingState) => { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(sizing)); };
return ( <Box width="800px" maxWidth="100%" overflowX="auto"> <Table data={data} columns={columns} enableColumnResizing initialColumnSizingState={savedWidths} onColumnSizingChange={onColumnSizingChange} > <TableContent /> </Table> </Box> );}