Tables
The Table component is an all inclusive and accessible table based on correct HTML semantics.
Please use the properties instead of overwriting the styles. And if you miss a feature, get in touch with us.
NB: If you have more than three (3) columns, please consider to use border
property in order to enhance accessibility.
Accessibility
Tables do both serve as a way of navigation for screen readers and other assertive technologies. But they also help to give data an ordered structure.
Use the documentation from MDN – The Table element for more information on making semantic correct tables, including scope
, align
, colSpan
and rowSpan
.
Here is a list of things you may follow along in order to ensure your coded tables still are accessible:
- Keep a semantic correct structure.
- Let tables align the column width, when possible.
- Do not use CSS
display
property on any table element. - Do not overwrite styles in general, but rather get in touch with DNB UX.
- Never put a table inside a table.
- Text inside tables do not need to be wrapped inside a paragraph as well. They give screen readers no additional useful information.
Table header components
<Th.SortButton />
to be used for additional sorting functionality.<Th.HelpButton />
to be used for help related content.
Alignment
Use e.g. align="right"
on a <Th>
, <Td>
or <Tr>
to align a table header or a table data element.
Fixed layout
You may consider using table-layout: fixed;
. You can use the modifier property fixed
for doing so and combine it with CSS e.g. width: 40%
on specific table headers.
Scrollable
Depending on your situation, you may want to wrap your Table within Table.ScrollView
:
import { Table } from '@dnb/eufemia'render(<Table.ScrollView><Table /></Table.ScrollView>,)
Sticky header
You have two options (both have their downsides):
-
use
sticky={true}
. It works even when using aTable.ScrollView
or aoverflow: hidden;
is used on any parent elements. And it works inside a Drawer as well. The downside is, that it uses JavaScript and the browser may drop some frames, which results in a potential flickering during scrolling. -
use
sticky="css-position"
for using the CSSposition: sticky;
method. It is super smooth. But then you can not use aoverflow: hidden;
oroverflow: auto;
on any parent elements. This is a know issue happening on every modern browser.
Method no. 2 should be used when a max-height
is set to the wrapping Table.ScrollView
e.g.:
<Table.ScrollView style={{ maxHeight: '20rem' }}><Table sticky="css-position" /></Table.ScrollView>
Have a look at this example.
Sortable table
Optionally, make use of the following React Hook to handle the Th.SortButton
directions.
It can be used as a "controller" for your own sorting logic of your data.
By default, it will cycle trough three stages ['asc', 'desc', 'off']
.
Show how to use the useHandleSortState React Hook.
import useHandleSortState from '@dnb/eufemia/components/table/useHandleSortState'// You can also provide a default that will be used as the fallback e.g.const defaultOptions = { direction: 'asc', modes: ['asc', 'desc', 'off'] }export const YourComponent = () => {const { sortState, sortHandler, activeSortName } = useHandleSortState({// Define your column names with options (optional)column1: { active: true }, //column2: { direction: 'desc', modes: ['asc', 'desc'] }, // overwrite the defaultOptionscolumn3: { modes: ['asc', 'off'] }, // will only allow one directioncolumn4: {}, // etc.},defaultOptions,)// Use these properties for your custom sorting logicconsole.log(sortState.column1.direction) // returns either "asc", "desc" or "off"console.log(activeSortName) // returns the current active one: "column1" (returns null when nothing is active)// Handle your logicuseEffect(() => {switch (sortState.column1.direction) {default:case 'asc':setYourLocalState(mockData.sort(compareFunctionAsc))breakcase 'desc':setYourLocalState(mockData.sort(compareFunctionsDesc))breakcase 'off':setYourLocalState(mockData)break}}, [sortState.column1.direction])return (<Table><thead><Tr><Thsortableactive={sortState.column1.active}reversed={sortState.column1.reversed}><Th.SortButtontext="Column 1"title="Sort this column"on_click={sortHandler.column1}/></Th></Tr></thead></Table>)}
Demos
Basic table
NB: In this example, the sort buttons do react on your input. But will not change the table data.
SyntaxError: Unexpected token (1:3)
Complex table
You can force a row to overwrite the automated odd/even counting by providing e.g. variant="even"
to a <Tr />
. You can use this in combination with rowSpan
.
NB: The table header in the first column needs to have scope="row"
!
Column 2 newline | Column 3 that spans | ||
---|---|---|---|
Row 1+2 Header | Row 1 that spans | Row 1 | Row 1 |
Row 2 | Row 2 | ||
Row 3 Header newline | Row 3 | noSpacing + align="right" | |
Row 4 Header | Row 4 | Row 4 |
Row scope headers only
This table has only scope="row"
and scope="rowgroup"
headers – without the default scope="col"
.
Header A | Row 1 | Row 1 |
---|---|---|
Header B | Row 2 | Row 2 |
Fixed table
Error: Unexpected token when processing JSX children.
Medium and small sized
Column | Column | |
---|---|---|
Row 1 | Row 1 | Row 1 |
Row 2 with paragraph | Row 2 with medium paragraph | Row 2 with medium text |
A small
sized table is only for special circumstances, where a lot of data needs to be shown on the screen at the same time.
Column | Column | |
---|---|---|
Row 1 | Row 1 | Row 1 |
Row 2 with paragraph | Row 2 with medium paragraph | Row 2 with medium text |
Table with accordion rows
The second example uses both a border
and an outline
.
SyntaxError: Unexpected token (1:3)
Table with sticky header
Header | |||
---|---|---|---|
Row 1 with p | Row 1 with code | Row 1 with span | Row 1 |
Column which spans over two columns | Row 2 | Row 2 | |
Row 3 | Row 3 | Row 3 | Row 3 |
Footer | Sum |
Table with a max height
A sticky table header with sticky="css-position"
and max-height
on the Table.ScrollView
.
Column 1 | Column 2 | Column 3 | Column 4 |
---|---|---|---|
Row 1 | Row 1 | Row 1 | Row 1 |
Row 2 | Row 2 | Row 2 | Row 2 |
Row 3 | Row 3 | Row 3 | Row 3 |
Row 4 | Row 4 | Row 4 | Row 4 |
Row 5 | Row 5 | Row 5 | Row 5 |
Row 6 | Row 6 | Row 6 | Row 6 |
Row 7 | Row 7 | Row 7 | Row 7 |
Row 8 | Row 8 | Row 8 | Row 8 |
Row 9 | Row 9 | Row 9 | Row 9 |
Row 10 | Row 10 | Row 10 | Row 10 |
Row 11 | Row 11 | Row 11 | Row 11 |
Row 12 | Row 12 | Row 12 | Row 12 |
Several tables in one container
Show how the import and syntax is structured.
<TableContainer> <TableContainer.Head> <H2>Heading</H2> </TableContainer.Head> <TableContainer.Body> <Table>{'hei'}</Table> <Table>{'hei'}</Table> </TableContainer.Body> <TableContainer.Foot> <P>Footer</P> </TableContainer.Foot> </TableContainer>
Error: Unexpected token when processing JSX children.
With no (empty) head
and foot
content.
Table with long header text (wrapping)
Static long header senectus ornare convallis ut at erat imperdiet commodo | |||
---|---|---|---|
col span of 4 |
Table with pagination
SyntaxError: Unexpected token (1:3)
Example usage without and with classes
Header | ||
---|---|---|
Row 1 | Row 1 | Row 1 |
Row 2 | Row 2 | Row 2 |
Row 3 | Row 3 | Row 3 |
.dnb-table__th | ||
---|---|---|
.dnb-table__tr--even > .dnb-table__td | ||
.dnb-table__tr--odd > .dnb-table__td |