Tables

The following table has a default style. But in future, there will be several extensions and styles to choose from.

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):

  1. use sticky={true}. It works even when using a Table.ScrollView or a overflow: 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.

  2. use sticky="css-position" for using the CSS position: sticky; method. It is super smooth. But then you can not use a overflow: hidden; or overflow: 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(
{
// Defiend your column names with options (optional)
column1: { active: true }, //
column2: { direction: 'desc', modes: ['asc', 'desc'] }, // overwrite the defaultOptions
column3: { modes: ['asc', 'off'] }, // will only allow one direciton
column4: {}, // etc.
},
defaultOptions
)
// Use these properties for your custom sorting logic
console.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 logic
useEffect(() => {
switch (sortState.column1.direction) {
default:
case 'asc':
setYourLocalState(mockData.sort(compareFunctionAsc))
break
case 'desc':
setYourLocalState(mockData.sort(compareFunctionsDesc))
break
case 'off':
setYourLocalState(mockData)
break
}
}, [sortState.column1.direction])
return (
<Table>
<thead>
<Tr>
<Th
sortable
active={sortState.column1.active}
reversed={sortState.column1.reversed}
>
<Th.SortButton
text="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.

A Table Caption
Column
Help Button
Row 1Row 1Row 1Row 1
Row 2Row 2Row 2Row 2

Row 3 with paragraph

Row 3 with code

Row 3 with medium paragraph

Row 3 with medium text

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.

A Table Caption
Column 2
newline
Column 3 that spans
Row 1 HeaderRow 1 that spansRow 1Row 1
Row 2 HeaderRow 2Row 2
Row 3 Header
newline
Row 3noSpacing + align="right"
Row 4 HeaderRow 4Row 4

Fixed table

A Table Caption
Column 1Column 2Column 3Column 4Column 5Column 6Column 7Column 8
Row 1Row 1Row 1Row 1Row 1Row 1Row 1Row 1
Row 2Row 2Row 2Row 2Row 2Row 2Row 2Row 2
Row 3Row 3Row 3Row 3Row 3Row 3Row 3Row 3
Row 4Row 4Row 4Row 4Row 4Row 4Row 4Row 4

Medium and small sized

A Table Caption
ColumnColumn
Row 1Row 1Row 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.

A Table Caption
ColumnColumn
Row 1Row 1Row 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.

A Table Caption
Column AColumn BColumn CColumn D
Row 1Row 1
Row 2Row 2
Row 3Row 3
A Table Caption
Column AColumn BColumn CColumn D
Row 1Row 1Row 1
Row 2Row 2Row 2
Row 3Row 3Row 3

Table with sticky header

A Table Caption
Header

Row 1 with p

Row 1 with codeRow 1 with spanRow 1
Column which spans over two columnsRow 2Row 2
Row 3Row 3Row 3Row 3
FooterSum

Table with a max height

A sticky table header with sticky="css-position" and max-height on the Table.ScrollView.

Column 1Column 2Column 3Column 4
Row 1Row 1Row 1Row 1
Row 2Row 2Row 2Row 2
Row 3Row 3Row 3Row 3
Row 4Row 4Row 4Row 4
Row 5Row 5Row 5Row 5
Row 6Row 6Row 6Row 6
Row 7Row 7Row 7Row 7
Row 8Row 8Row 8Row 8
Row 9Row 9Row 9Row 9
Row 10Row 10Row 10Row 10
Row 11Row 11Row 11Row 11
Row 12Row 12Row 12Row 12

Several tables in one container

Show how the import and syntax is structured.
import TableContainer from '@dnb/eufemia/components/table/TableContainer'
render(
<TableContainer>
<TableContainer.Head>
<H2>Heading</H2>
</TableContainer.Head>
<TableContainer.Body>
<Table />
<Table />
</TableContainer.Body>
<TableContainer.Foot>
<P>Footer</P>
</TableContainer.Foot>
</TableContainer>
)

Header

Text

Table One
I have a superscript 1Column 2Column 3Column 4
Row 1Row 1Row 1Row 1
Row 2Row 2Row 2Row 2
Table Two
Column 1Column 2Column 3Column 4
Row 1Row 1Row 1Row 1
Row 2Row 2Row 2
Row 3Row 3Row 3

Footer

Table with long header text (wrapping)

A Table Caption
Static long header senectus ornare convallis ut at erat imperdiet commodo

col span of 4

Table with pagination

Side 1

Example usage of class helpers

.dnb-table__th
.dnb-table__tr--even > .dnb-table__td
.dnb-table__tr--odd > .dnb-table__td