Table
Data tables are used to present a set of highly structured data that is easy for the user to scan, compare and analyze.
# (Data) table v.s List table
To clarify the differences between Table
and ListTable
, a short description is provided.
Component name | Usage |
---|---|
(Data) table | Used to organize and present data in a way that helps the user compare and analyze it. |
List table | Used to list a collection of objects of the same type, such as policies, to help the user find an object and navigate to a full-page representation of it. |
#Examples
#Basic usage
You can use a Table
to structure both static and interactive data, arranged in rows and columns. Since the Table
is designed for use cases that are focused on handling large amounts of tabular data, it must contain a Column header for sorting. The component provides tools such as buttons, filters, search and export to ensure that UI elements remain consistent and the user is given the flexibility to gain relevant insights from the data set.
A Table
component contains four elements:
- Column header: the labels for each column in the table. The Column header can sort the data in ascending or descending order. However, the default order of the columns should reflect the importance of the data to the user, and related columns should be adjacent. See Column header.
- Rows: each row contains the same number of cells and the content related to the corresponding Column header. The rows can be expanded and highlighted to emphasize specific data.
- Table Toolbar (optional): a flexible container that provides access to several table related functions, such as actions, search, filtering and export. See Table Toolbar.
- Pagination (optional): allow the user to navigate data as pages when the amount of data is too large to be displayed at once. See Pagination.
Lasagna | 45 | 2 |
---|---|---|
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
"data-observe-key": "table-header-dish",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
content: "Cook Time",
tooltip: "in minutes",
"data-observe-key": "table-header-cook-time",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
tooltip: "in persons",
notSortable: true,
"data-observe-key": "table-header-servings",
},
render: (dto) => dto.servings,
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
caption="Basic usage table"
/>
);
#Usage with pagination
A Pagination allows the user to navigate through a collection of items split across multiple pages of data, and is displayed below the bottom row.
A Pagination includes:
- Text to show the total number of items, including the currently displayed items.
- Button groups to navigate to the first, previous, next and last page.
- A Select to specify a page number.
- A Select to specify the number of items per page.
const lotsOfData: { id: number; title: string; cookTime: number; servings: number }[] = [];
for (let i = 1; i < 16; i++) {
lotsOfData.push({
id: i,
title: `Recipe ${i}`,
cookTime: 5 * i,
servings: Math.floor(i / 25) + 1,
});
}
const [sort, setSort] = useState<SortField<typeof lotsOfData[0]>>({
property: "title",
direction: "asc",
});
const [items, setItems] = useState(sortItems(lotsOfData, sort));
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(5);
const pagedItems = items.slice((page - 1) * pageSize, page * pageSize);
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={pagedItems}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
pagination={{
total: items.length,
count: pagedItems.length,
page: page,
setPage: setPage,
pageSize: pageSize,
setPageSize: setPageSize,
cancelLabel: "Cancel",
confirmLabel: "Confirm",
firstLabel: "First",
prevLabel: "Previous",
nextLabel: "Next",
lastLabel: "Last",
pagingInfoLabel: (startIdx: number, endIdx: number, total: number) =>
`${startIdx} - ${endIdx} of ${total} items`,
pageLabel: "Page",
pageXofYLabel: (current: number, total: number) => `Page ${current} of ${total}`,
pageSizeSelectionLabel: (pageSize: number) => `${pageSize} items`,
pageSizeSelectorPrefix: "Show",
pageSizeSelectorPostfix: "per page",
pageSizeLabel: "Items per page",
defaultError: "Default pagination error",
wholeNumberError: "Must be a whole number",
outOfBoundsError: (total: number) => `Enter a number between 1 and ${total}`,
}}
/>
);
#Usage with search
Allow the user to search for specific data that meets specific criteria. To ensure consistency, use an input field within the Table Toolbar component.
The user can perform a table-wide keyword search on all cell values that have been indexed for the search. The search function works with the default typeahead functionality of the standard search component. When you perform a search query, the table is updated to show only those rows whose values match the search term.
Lasagna | 45 | 2 |
---|---|---|
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [query, setQuery] = useState("");
const displayedItems = items.filter((x) => x.title.toLowerCase().startsWith(query.toLowerCase()));
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<>
<TableToolbar
search={
<InputFieldWithSlug
value={query}
onChange={setQuery}
placeholder="Search by dish title"
rightSlug={
<Button onClick={() => console.log(query)} aria-label="Submit search">
<Icon>
<IconSearch />
</Icon>
</Button>
}
/>
}
/>
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={displayedItems}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
/>
</>
);
#Usage with filters
In addition to searching, the user can use the filter function to narrow and refine the rows displayed in the Table
. To ensure consistency, use filters within the Table Toolbar component.
Depending on the configuration chosen during implementation, filtering can be applied as follows:
- Table-wide filtering: allows the user to filter the data by all attributes present in any of the data columns.
- Custom column filtering: allows the user to focus the filtering on the attributes within a single column of data.
Filters that control how Table
data is displayed should be placed directly over a Table
. The syntax of filters should be transparent to users, and they should easily recognize that they are seeing filtered data. Always make sure that filters have a clear visual indication of their active state.
Lasagna | 45 | 2 |
---|---|---|
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
type CookingTime = { id: number; name: string };
const cookingTimes: CookingTime[] = [
{ id: 1, name: "All cooking times" },
{ id: 2, name: "Max. 30 min" },
{ id: 3, name: "Max. 60 min" },
];
const [cookingTime, setCookingTime] = useState<CookingTime | undefined>(cookingTimes[0]);
const onChange = (newValue: CookingTime | undefined) => {
console.log("Filter changed, calling API with new cooking time", newValue);
setCookingTime(newValue);
};
const [filterButton, activeFilters] = useSingleFilter(cookingTime, onChange, {
label: "Cooking time",
stringify: (cookingTime) => cookingTime?.name,
items: cookingTimes.map((cookingTime) => ({ title: cookingTime.name, value: cookingTime })),
compareFn: (a, b) => a.id === b.id,
defaultOption: cookingTimes[0],
});
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const displayedItems =
cookingTime?.id === 1
? items
: items.filter((x) => (cookingTime?.id === 2 ? x.cookTime <= 30 : x.cookTime <= 60));
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<>
<TableToolbar filter={filterButton} activeFilters={activeFilters} />
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={displayedItems}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
/>
</>
);
#Usage with expandable content
Use expandable content to reduce visual noise and make it easier to read the content that is most important for the task at hand. Clicking on the cell expands the row to fill the width of the Table
, and if needed, the contents of the expanded row can be accessed further by scrolling. Avoid a lot of interaction and do not take up more than 50% of the screen. If you need to expand the page to display dense, highly interactive content, use a new page for that purpose instead.
Expandable rows serve two purposes for users:
- Allow users to view simple and additional information.
- Users can reduce the width of the cells for content they consider less important.
Note that data loading can occur the moment the row is expanded. In this case, you can use a Spinner to tell the user that the content is loading.
Lasagna | 2 | |
---|---|---|
Pancakes | 20 | 4 |
Sushi | 6 | |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
expandOptions: {
canCellExpand: (item, pos) => pos.rowNum === 0 || item.cookTime > 30,
cellExpandRenderer: (item) => (
<span>Expanded {item.title}. You can place graphs, tables, etc here!</span>
),
},
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
/>
);
#Usage with highlight
The highlighted effect allows the user to focus on a single row at a time, especially when you have multiple columns and data points. The highlighted row uses the background color $color--background--interactive--selected
(#EBF6FF
). You can use this effect to highlight a selected row if the row contains an interactive element, such as Checkbox.
Lasagna | 45 | 2 |
---|---|---|
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
highlightRow={(i) => i.title === "Lasagna"}
/>
);
#Usage with data-observe-keys on table cells
Use data-observe-key
on table cells to create an identifier, which is useful for tracking user interactivity, for example.
Cook Time | ||
---|---|---|
Lasagna | 45 | 2 |
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
const observeKeyPrefix = "observe-key-example";
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
dataObserveKeys: (item) => {
return `${observeKeyPrefix}-dish-column-${item.title}`;
},
},
},
{
header: {
content: "Cook Time",
},
render: (dto) => dto.cookTime,
options: {
dataObserveKeys: (item, pos) => {
return `${observeKeyPrefix}-cook-column-${pos.columnNum}-row-${pos.rowNum}`;
},
},
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
options: {
dataObserveKeys: (item, pos) => {
return pos.rowNum === 0
? `${observeKeyPrefix}-servings-column-row-${pos.rowNum}`
: undefined;
},
},
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
/>
);
#Usage with summary
Include a summary row that displays column totals and averages. This helps the user to get a quick overview of certain data. For example, the Analytics site statistics overview displays the percentage of total page views per device/page and the average bounce rate for each site.
46.25 average | 20 total | |
---|---|---|
Lasagna | 45 | 2 |
Pancakes | 20 | 4 |
Sushi | 90 | 6 |
Cake | 30 | 8 |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
const cookTimeAverage = useMemo<number>(() => {
const cookTimeSum = items.reduce((sum, item) => sum + item.cookTime, 0);
return cookTimeSum / items.length;
}, [items]);
const servingsTotal = useMemo<number>(
() => items.reduce((sum, item) => sum + item.servings, 0),
[items]
);
return (
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
content: "Cook Time",
tooltip: "in minutes",
},
summary: {
value: cookTimeAverage,
label: "average",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
tooltip: "in persons",
notSortable: true,
},
summary: {
value: servingsTotal,
label: "total",
},
render: (dto) => dto.servings,
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
caption="Basic usage table"
/>
);
#Usage with exporter
Using our TableExporter
component or the useTableExport
hook, developers have access to a Modal
that allows the user to decide which table pages he wants to export (current page or all pages). The user can also choose whether or not to export subtables (expanded content). With these user settings, the developer can proceed and call his own export routine.
If you have a Table
within a Card
, please display the export button explicitly. Do not place the export button inside the ActionMenu
on the right edge of a Card
. This is because the export button is for downloading the table data and not all the information on the Card
. Sometimes, there might be other UI elements in the Card
, such as Tab
or ContentSwitcher
that export data that the user may not initially need.
const lotsOfData: { id: number; title: string; cookTime: number; servings: number }[] = [];
for (let i = 1; i < 16; i++) {
lotsOfData.push({
id: i,
title: `Recipe ${i}`,
cookTime: 5 * i,
servings: Math.floor(i / 25) + 1,
});
}
const [loading, setLoading] = useState(true);
const [sort, setSort] = useState<SortField<typeof lotsOfData[0]>>({
property: "title",
direction: "asc",
});
const [items, setItems] = useState(sortItems(lotsOfData, sort));
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(5);
const pagedItems = items.slice((page - 1) * pageSize, page * pageSize);
const getCookTimeData = (item: typeof lotsOfData[0]) => {
return [
{ cookMode: "Baked", cookTime: item.cookTime },
{ cookMode: "Boiled", cookTime: item.cookTime + 5 },
{ cookMode: "Grilled", cookTime: item.cookTime + 10 },
];
};
const MyExporter = () => (
<TableExporter
onExport={(opt) => {
console.log(
"Call your export routine (front-end or back-end) with the following options",
opt
);
}}
buttonContent={
<>
<Icon>
<IconDownload />
</Icon>
<InlineText>Export</InlineText>
</>
}
modalTitle="Export"
pageSize={pageSize}
total={lotsOfData.length}
expandableProperties={[{ propertyName: "cookTime", displayName: "Cook time" }]}
/>
);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<>
<TableToolbar exports={<MyExporter />} />
<Table
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
expandOptions: {
canCellExpand: () => true,
cellExpandRenderer: (item) => (
<CookTimeSubTable item={item} data={getCookTimeData(item)} />
),
},
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
},
]}
items={pagedItems}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
pagination={{
total: items.length,
count: pagedItems.length,
page: page,
setPage: setPage,
pageSize: pageSize,
setPageSize: setPageSize,
cancelLabel: "Cancel",
confirmLabel: "Confirm",
firstLabel: "First",
prevLabel: "Previous",
nextLabel: "Next",
lastLabel: "Last",
pagingInfoLabel: (startIdx: number, endIdx: number, total: number) =>
`${startIdx} - ${endIdx} of ${total} items`,
pageLabel: "Page",
pageXofYLabel: (current: number, total: number) => `Page ${current} of ${total}`,
pageSizeSelectionLabel: (pageSize: number) => `${pageSize} items`,
pageSizeSelectorPrefix: "Show",
pageSizeSelectorPostfix: "per page",
pageSizeLabel: "Items per page",
defaultError: "Default pagination error",
wholeNumberError: "Must be a whole number",
outOfBoundsError: (total: number) => `Enter a number between 1 and ${total}`,
}}
/>
</>
);
#Usage with CSV exporter
Raw tabular data is data that can be exported to a CSV file. It contains no formatting and is identical to the source file.
The "Export to CSV" button is the most common secondary action for the user. Thus we offer a built-in CSV exporter for free through the TableCsvExporter
component, which supports exporting subtables, custom filename, custom header and footer, custom delimiter, etc. The CSV representation of each column will be inferred automatically if possible, otherwise, developers should define it by using the csv
property on each column config, as we can see in the example code. Subtable settings must always be defined by the developer.
const lotsOfData: { id: number; title: string; cookTime: number; servings: number }[] = [];
for (let i = 1; i < 16; i++) {
lotsOfData.push({
id: i,
title: `Recipe ${i}`,
cookTime: 5 * i,
servings: Math.floor(i / 25) + 1,
});
}
const [loading, setLoading] = useState(true);
const [sort, setSort] = useState<SortField<typeof lotsOfData[0]>>({
property: "title",
direction: "asc",
});
const [items, setItems] = useState(sortItems(lotsOfData, sort));
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(5);
const pagedItems = items.slice((page - 1) * pageSize, page * pageSize);
type CookTimeData = { cookMode: string; cookTime: number };
const getCookTimeData = (item: typeof lotsOfData[0]): CookTimeData[] => {
return [
{ cookMode: "Baked", cookTime: item.cookTime },
{ cookMode: "Boiled", cookTime: item.cookTime + 5 },
{ cookMode: "Grilled", cookTime: item.cookTime + 10 },
];
};
const columns: ColumnsArray<typeof lotsOfData[0]> = [
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
property: "cookTime",
content: "Cook Time",
},
render: (dto) => dto.cookTime,
expandOptions: {
canCellExpand: () => true,
cellExpandRenderer: (item) => <CookTimeSubTable item={item} data={getCookTimeData(item)} />,
},
csv: {
subTable: {
dataProvider: async (item) => getCookTimeData(item),
property: "cookTime",
columns: [
{
header: "Cook Mode",
render: (dto) => dto.cookMode,
},
{
header: "Cook Time",
render: (dto) => dto.cookTime,
},
],
},
} as CsvColumnWithSubTableConfig<typeof lotsOfData[0], CookTimeData>,
},
{
header: {
property: "servings",
content: "Servings",
},
render: (dto) => dto.servings,
csv: {
header: "Servings (per person)", // you can override the header for the CSV export
render: (dto) => dto.servings, // you can override the render function for the CSV export
},
},
];
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<>
<TableToolbar
exports={
<TableCsvExporter
columns={columns}
pageNumber={page}
pageSize={pageSize}
total={lotsOfData.length}
expandableProperties={[{ propertyName: "cookTime", displayName: "Cook time" }]}
fileName="table-example.csv"
contentPre={`Table Example\n${escapeCsvContent(new Date().toLocaleString())}\n\n`}
contentPos={"\n\nSiteimprove ©"}
dataProvider={async ({ pageNumber, pageSize }) =>
items.slice((pageNumber - 1) * pageSize, pageNumber * pageSize)
}
/>
}
/>
<Table
columns={columns}
items={pagedItems}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
pagination={{
total: items.length,
count: pagedItems.length,
page: page,
setPage: setPage,
pageSize: pageSize,
setPageSize: setPageSize,
cancelLabel: "Cancel",
confirmLabel: "Confirm",
firstLabel: "First",
prevLabel: "Previous",
nextLabel: "Next",
lastLabel: "Last",
pagingInfoLabel: (startIdx: number, endIdx: number, total: number) =>
`${startIdx} - ${endIdx} of ${total} items`,
pageLabel: "Page",
pageXofYLabel: (current: number, total: number) => `Page ${current} of ${total}`,
pageSizeSelectionLabel: (pageSize: number) => `${pageSize} items`,
pageSizeSelectorPrefix: "Show",
pageSizeSelectorPostfix: "per page",
pageSizeLabel: "Items per page",
defaultError: "Default pagination error",
wholeNumberError: "Must be a whole number",
outOfBoundsError: (total: number) => `Enter a number between 1 and ${total}`,
}}
/>
</>
);
#Combined columns
You can combine columns to create a new column that contains the combined data of the original columns. This is useful when you want to display related data in a single column, but still want to sort and filter the data based on the original columns.
1 - Lasagna | 45 min 2 persons |
---|---|
2 - Pancakes | 20 min 4 persons |
3 - Sushi | 90 min 6 persons |
4 - Cake | 30 min 8 persons |
const [items, setItems] = useState(someData);
const [sort, setSort] = useState<SortField<typeof items[0]>>({
property: "title",
direction: "asc",
});
const [loading, setLoading] = useState(true);
useEffect(() => {
setItems(sortItems(items, sort));
setLoading(false);
}, [sort]);
return (
<Table
columns={[
{
header: [
{
property: "id",
content: "ID",
defaultSortDirection: "asc",
"data-observe-key": "table-header-id",
},
{
property: "title",
content: "Dish",
defaultSortDirection: "asc",
"data-observe-key": "table-header-dish",
},
],
render: (dto) => `${dto.id} - ${dto.title}`,
options: {
isKeyColumn: true,
},
},
{
header: [
{
property: "cookTime",
content: "Cook Time",
tooltip: "in minutes",
"data-observe-key": "table-header-cook-time",
},
{
property: "servings",
content: "Servings",
tooltip: "in persons",
notSortable: true,
"data-observe-key": "table-header-servings",
},
],
render: (dto) => (
<BigSmall big={`${dto.cookTime} min`} small={`${dto.servings} persons`} />
),
},
]}
items={items}
sort={sort}
setSort={(property, direction) => {
setLoading(true);
setSort({
property: property,
direction: property === sort.property ? invertDirection(sort.direction) : direction,
});
}}
loading={loading}
caption="Basic usage table"
/>
);
#Loading state
The loading state is used to indicate that the data of the table is still being loaded.
Loading |
<Table
loading={true}
items={[] as typeof someData}
caption="Table with loading state enabled"
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
content: "Cook Time",
tooltip: "in minutes",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
tooltip: "in persons",
notSortable: true,
"data-observe-key": "table-header-servings",
},
render: (dto) => dto.servings,
},
]}
sort={{ property: "title", direction: "asc" }}
setSort={() => {}}
/>
#No data state
The noDataState
is used to indicate that no data is available for display. An Empty State component with the type
"reassure" and default heading is shown, but it can be overridden by another type with custom text. For guidelines please refer to the Empty State component.
To avoid confusion, in cases where the user has resolved the issues, explain why the data cannot be displayed. E.g. "Accessibility issues have been resolved".
No data to display |
<Table
loading={false}
items={[] as typeof someData}
caption="Table with no data"
columns={[
{
header: {
property: "title",
content: "Dish",
defaultSortDirection: "asc",
},
render: (dto) => dto.title,
options: {
isKeyColumn: true,
},
},
{
header: {
content: "Cook Time",
tooltip: "in minutes",
},
render: (dto) => dto.cookTime,
},
{
header: {
property: "servings",
content: "Servings",
tooltip: "in persons",
notSortable: true,
"data-observe-key": "table-header-servings",
},
render: (dto) => dto.servings,
},
]}
sort={{ property: "title", direction: "asc" }}
setSort={() => {}}
/>
#Properties
1 | Number 1 |
---|---|
2 | Number 2 |
3 | Number 3 |
4 | Number 4 |
Property | Description | Defined | Value |
---|---|---|---|
columnsRequired | type-union[] Configurations for the columns displayed in the table | ||
itemsRequired | unknown[] Items displayed in the table | ||
sortRequired | object Property and direction by which the table is sorted | ||
setSortRequired | function Callback for updating sorting | ||
loadingRequired | boolean Is the table in a loading state? | ||
loadingTextOptional | string Optional text to be displayed when the table is loading | ||
element Optional footer to be displayed below the table | |||
paginationOptional | object Pagination | ||
topAlignedOptional | boolean Sets the data inside the table to be top-aligned | ||
highlightRowOptional | function Compare function for highlight row | ||
rowKeyOptional | function Function to generate a key for rows | ||
string Caption for the table | |||
noDataStateOptional | element Content to be shown when there's no data. An Empty State component is shown by default, but it can be overridden by another type with custom text. | ||
withoutKeyColumnOptional | boolean Does this table not have a key column? Be sure this is the case otherwise the table is less accessible | ||
data-observe-keyOptional | string Unique string, used by external script e.g. for event tracking | ||
classNameOptional | string Custom className that's applied to the outermost element (only intended for special cases) | ||
styleOptional | object Style object to apply custom inline styles (only intended for special cases) |
#Guidelines
#Best practices
#Do not use when
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications