Skip to content
lab components / Tables and lists

Base table

A simplified table component for displaying structured data in a clean, accessible format.

This is a Lab component!

That means it doesn't satisfy our definition of done and may be changed or even deleted. For an exact status, please reach out to the Fancy team through the dev_fancy or ux_fancy channels.

import { BaseTable } from "@siteimprove/fancylab";

#Table vs. Base Table

ComponentPurposeUsage
TableA feature-rich table component optimized for complex data interactions. Offers sorting, filtering, pagination, row selection, etc.Displaying extensive datasets requiring user manipulation.
Base tableA streamlined table for presenting structured data without advanced interactions.Displaying smaller datasets within dashboards or cards where complex interactions are not the primary focus.

#Examples

#Basic usage

This will render a basic table with headers and data cells.

Header
foo
bar
<BaseTable items={[{ text: "foo" }, { text: "bar" }]} columns={[{ header: "Header", render: (item) => <div>{item.text}</div> }]} withoutKeyColumn />

#With caption

The caption provides a brief description of the table's content, aiding screen reader users and improving overall comprehension.

Caption for table
Header
foo
bar
<BaseTable items={[{ text: "foo" }, { text: "bar" }]} columns={[{ header: "Header", render: (item) => <div>{item.text}</div> }]} caption="Caption for table" withoutKeyColumn />

#Expandable cells

Enable users to delve deeper into specific data points by making cells expandable. This allows for the display of additional details or actions within the cell.

Columns can be marked expandable.isExpandable is a function that gets called for each cell so you can control exactly which ones are expandable.

Header
foo
<BaseTable items={[ { text: "foo", count: 0 }, { text: "bar", count: 1 }, ]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isExpandable: (item) => item.count > 0, }, ]} withoutKeyColumn />

#With row highlight

Visually emphasize specific rows based on certain criteria.

You can do so by providing a function as highlightRow prop which takes an item as parameter and returns a boolean which indicates whether or not to highlight an item. This function gets called for each row and adds a highlight class to those matching the criteria.

HeaderCount
foo
0
bar
1
<BaseTable items={[ { text: "foo", count: 0 }, { text: "bar", count: 1 }, ]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isKeyColumn: true }, { header: "Count", render: (item) => <div>{item.count}</div> }, ]} highlightRow={(item) => item.text === "foo"} />

#With column summary

You can also display an extra row in the table header that contains the summary of a corresponding column. The summary content is formed by a number value and a label. The number value can also be provided as a string, enabling the use of number formatters. To display the summary of a column, use the property summary in the specifications of a column, as exemplified below.

HeaderCount

1

total

foo
0
bar
1
<BaseTable items={[ { text: "foo", count: 0 }, { text: "bar", count: 1 }, ]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isKeyColumn: true }, { header: "Count", render: (item) => <div>{item.count}</div>, summary: { value: 1, label: "total", }, }, ]} />

#Keeping state

You might've noticed in the Expandable cells example that nothing happens when clicking the expander. Why? Because BaseTable is stateless. It's not its concern to know what's expanded, only to render that it is expandable. Lets hook it up so expanding works. In the below example note how we keep track of state ourselves, and push that state into BaseTable so it always renders the right thing.

Header
const [state, setState] = useState<BaseTableCellPosition>(); return ( <BaseTable items={[{ text: "foo" }, { text: "bar" }]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isExpandable: () => true, }, ]} withoutKeyColumn expandedContentRenderer={() => <div>I 💓 expanded content</div>} expandedCellPosition={state} onExpandClick={(e, cellPosition) => { // If the user clicks an already-expanded cell we want to collapse, i.e. reset state if ( state && state.columnNum == cellPosition.columnNum && state.rowNum == cellPosition.rowNum ) { setState(undefined); return; } setState(cellPosition); }} /> );

#Loading state

When fetching data, provide a visual indicator (e.g. Spinner) to inform the user that the table is loading.

HeaderCount

Loading items

<BaseTable<{ text: string; count: number }> loading={true} loadingText="Loading items" items={[]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isKeyColumn: true }, { header: "Count", render: (item) => <div>{item.count}</div> }, ]} />

#No data state

When no data is available, display a clear, informative message ( e.g. "Accessibility issues have been resolved."). Consider the context and provide guidance on how to resolve the issue if possible.

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.

HeaderCount

No data to display

<BaseTable<{ text: string; count: number }> items={[]} columns={[ { header: "Header", render: (item) => <div>{item.text}</div>, isKeyColumn: true }, { header: "Count", render: (item) => <div>{item.count}</div> }, ]} />

#Properties

Header 1Header 2
foo
bar
bar
foo
PropertyDescriptionDefinedValue
columnsRequired
object[]Column configurations
itemsRequired
unknown[]Items to be displayed
onExpandClickOptional
functionCallback for when expander is clicked
expandedContentRendererOptional
functionRenderer for content of expanded cell
expandedCellPositionOptional
objectPosition of the expanded cell
elementFooter displayed below table data
topAlignedOptional
booleanIs the content top aligned?
highlightRowOptional
functionCompare function for highlight row
rowKeyOptional
functionFunction to generate a key for rows
captionOptional
stringCaption for the table
withoutKeyColumnOptional
booleanDoes this table not have a key column? Be sure this is the case otherwise the table is less accessible
thClassNameOptional
stringClassName for th elements
trClassNameOptional
stringClassName for tr elements
tdClassNameOptional
stringClassName for td elements
loadingOptional
booleanIs the table in a loading state?
loadingTextOptional
stringOptional text to be displayed when the table is loading
noDataStateOptional
elementContent 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.
data-componentOptional
stringName of the component. Should only be set by components since it needs to stable. Used to track component usage
data-observe-keyOptional
stringUnique string, used by external script e.g. for event tracking
classNameOptional
stringCustom className that's applied to the outermost element (only intended for special cases)
styleOptional
objectStyle object to apply custom inline styles (only intended for special cases)

#Guidelines

#Best practices

#General

Use BaseTable when

  • Presenting tabular data where visual clarity and comparison are prioritise.
  • Displaying smaller to medium-sized datasets.

#Placement

BaseTable are typically used in the following places:

  • Within Card: Provides detailed information within a Card component's content area.
  • On Dashboards: Summarizes key data points within a Dashboard widgets.

#Style

  • Siteimprove Design System: Adhere to Siteimprove's guidelines for color, typography, and spacing. If you are not using a component from Fancy, match the styling of your BaseTable to existing components for visual consistency.
  • Clarity and readability:
    • Column Formatting: Ensure consistency within each column using specialized components (FormattedDate, FormattedNumber, BigSmall, TitleUrl) for dates, numbers, and URLs.
    • Column Headers: Keep them concise, descriptive, and relevant to the data.
    • Visual Hierarchy: Employ visual cues (bold text, color) to emphasize important data points within the table.
  • Responsiveness: Design for optimal viewing on various screen sizes, ensuring the table adapts gracefully to different viewports.
  • Error/ Empty State: In case of errors fetching or no data displayed, present a user-friendly error message and offer suggestions for troubleshooting or retrying.

#Do not use when

  • Displaying large datasets. Use Table instead.
  • Showing simple lists or unstructured data. Use List or List Table instead
  • Data is better visualized in a different format. Use Chart instead.

#Accessibility

#For designers

  • Ensure sufficient color contrast between text and background.

#For developers

For tables that have more than one column, consider giving it a key column. The key column is useful for screen reader users, as it will describe the columns to its right side. A table should not have more than one key column, hence only the first column that is marked as key column will be used. The key column must not be the last column to the right. You can mark the column that contains the main information as a key column by setting the optional isKeyColumn prop in the column's config to true. This will add scope="row" to the key column's <th> tag, helping screen reader users better understand the table's content.

If the table should not have a key column, you can set the optional withoutKeyColumn prop of BaseTable to true, which will silence the console error the component prints when none of a table's column configs are marked as a key column.

Explore detailed guidelines for this component: Accessibility Specifications

#Writing

  • Keep column headers concise, descriptive, and relevant to the data.
  • Avoid excessive abbreviations or jargon.
  • Use sentence case of each header unless proper nouns and Siteimprove products and features are present. Read Capitalization rule.