Comparison Table - React
import { Table } from '@sky-uk/ui-core';
The Table component provides a means of displaying multiple configurable cells of tabular data.
Note: All Image components require the $aspectRatio prop to be applied
in order to allow the Table to correctly calculate the dimensions of the cells.
Breaking Change in v12:
The $tableId is a required prop in UI Core v12.0.0. This needs to be unique to each table instance.
Omitting this prop will result in the table rendering incorrectly and a console warning to be displayed.
<Container $size="10-col"> <Table $comparisonTable $stickyTop={80} $theme="sky" $tableId="example-1" tableAriaLabel="Comparison Example 1"> <Table.Switch /> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }} $textAlign="center" $width="100%"> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/75x75" order="1" $width="75px" $aspectRatio="1 / 1" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $fontSize={{ xs: 'display-7', lg: 'display-6' }} $textAlign="center" $width="100%"> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 9 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} <Table.Row $hideRowHeader> <Table.RowHeader>Row Header 10</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> <Button>Buy 10.{colIndex + 1}</Button> </Table.RowCell> ))} </Table.Row> </Table> </Container>
Props
Comparison Table
| Prop | Type | Default | Description |
| $columnsInView | object | {xs: 2, sm: 2, md: 3, lg: 4, xl: 5, xxl: 5} | The $columnsInView prop allows you to specify how many cells should be visible in the table at different breakpoints. This is useful for creating responsive tables that adapt to different screen sizes. |
| $columnFills | array | undefined | An array of background colours to be applied to each column. Disabled if $comparisonTable is true |
| $comparisonTable | boolean | false | When true applies a border to the first column, making it stick when scrolled horizontally. |
| $hideRowHeaders | boolean | false | When true, visually hides the Row.Header for each row. For accessibility reasons the Row.Header is still required on each row. |
| $keyLines | boolean | false | When true applies a keyline between each row. |
| $maxRows | number | undefined | When defined, limits the number of rows initially shown and displays a toggle button |
| $stickyTop | number | 0 | The point at which the Table.Header sticks at the top of the table. |
| $tableId | string | undefined | A unique Id for the Comparison Table. This prop is required in order to make the table render correctly |
| $theme | string | undefined | The theme of the comparison column border as a Sky gradient |
| $scrollableRegionTabIndex | boolean | false | If set, the table will be focusable. This is used for accessibility purposes. Note: this prop should only be used if the table has non-interactive content. |
| tableAriaLabel | string | Table | Sets the aria-label for the table's scrollable region, providing an accessible name for assistive technologies. This label is required for accessibility purposes. |
Table.Row
| Prop | Type | Default | Description |
| $hideHeader | boolean | undefined | Hides an individual Row.Header. For accessibility reasons the Row.Header is still required. |
$columnsInView
The $columnsInView prop allows you to specify how many cells should be visible in the table at different breakpoints. This is useful for creating responsive tables that adapt to different screen sizes.
<Container $size="10-col"> <Table $comparisonTable $tableId="example-in-view" $scrollableRegionTabIndex $stickyTop={80} $columnsInView={{xs: 2, sm: 3, md: 3, lg: 3, xl: 3, xxl: 3}} tableAriaLabel="Comparison Example 2"> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/75x75" order="1" $width="75px" $aspectRatio="1 / 1" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} <Table.Row $hideRowHeader> <Table.RowHeader>Row Header 10</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> <Button>Buy 10.{colIndex + 1}</Button> </Table.RowCell> ))} </Table.Row> </Table> </Container>
$columnFills
$columnsFills is an array property that applies a background color to specific columns within the </Table> component.
Each index in the array corresponds to a column, with color values determining the background color and null preserving
the default styling. The row header is excluded from this styling, as it is not intended to be coloured
<Container $size="10-col"> <Table $tableId="example-column-fills" $scrollableRegionTabIndex $stickyTop={80} $columnFills={[`${color('products.broadband')}20`, 'lightblue', null, color('grey5'), null]} tableAriaLabel="Comparison Example 3"> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/75x75" order="1" $width="75px" $aspectRatio="1 / 1" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} <Table.Row $hideRowHeader> <Table.RowHeader>Row Header 10</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> <Button>Buy 10.{colIndex + 1}</Button> </Table.RowCell> ))} </Table.Row> </Table> </Container>
Pagination
The pagination navigation element that allows users to scroll horizontally through table content. It will render automatically when scrolling is required.
<Container $size="10-col"> <Table $tableId="example-display-pagination" $stickyTop={80} $scrollableRegionTabIndex tableAriaLabel="Comparison Example 4"> <Table.Switch /> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell key={index}>Column Header {index + 1}</Table.HeaderCell> ))} </Table.Header> {Array.from({ length: 8 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} </Table> </Container>
$hideRowHeaders
$hideRowHeaders is a boolean property that, when enabled, visually hides the row header
in the </Table> component. The Row Headers are still required to be in the DOM for accessibility reasons.
This can be useful when row headers don't need to be displayed, but their information still needs to be accessed by assistive tech.
<Container $size="10-col"> <Table $tableId="example-hide-row-headers" $hideRowHeaders $scrollableRegionTabIndex tableAriaLabel="Comparison Example 5"> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/75x75" order="1" $width="75px" $aspectRatio="1 / 1" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} <Table.Row> <Table.RowHeader>Row Header 10</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> <Button>Buy 10.{colIndex + 1}</Button> </Table.RowCell> ))} </Table.Row> </Table> </Container>
$keyLines
$keyLines is a boolean property that, when enabled, adds a keyline between each row in the </Table> component.
This helps visually separate rows for improved readability and structure.
<Container $size="10-col"> <Table $tableId="example-hide-keylines" $keyLines $stickyTop={80} $scrollableRegionTabIndex tableAriaLabel="Comparison Example 6"> <Table.Header> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/75x75" order="1" $width="75px" $aspectRatio="1 / 1" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $flexDirection="column" $justifyContent="flex-start" $alignItems="center"> <Text $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Cell Row {rowIndex + 1} Col {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} <Table.Row> <Table.RowHeader $hideHeader>Row Header 10</Table.RowHeader> {Array.from({ length: 4 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> <Button>Buy 10.{colIndex + 1}</Button> </Table.RowCell> ))} </Table.Row> </Table> </Container>
$hideHeader
$hideHeader is a boolean property that, when enabled on a </Table.Row>, hides
the header for that specific row. This allows for more flexible table layouts where
individual rows can omit their headers as needed. For example below we can see how we
can apply it to the first for items in the row array.
<Container $size="10-col"> <Table $tableId="example-hide-row-header" $stickyTop={80} $scrollableRegionTabIndex tableAriaLabel="Comparison Example 7"> <Table.Header> {Array.from({ length: 5 }).map((_, index) => ( <Table.HeaderCell key={index} $paddingX={2} $paddingY={4}> Header {index} </Table.HeaderCell> ))} </Table.Header> {Array.from({ length: 6 }).map((_, rowIndex) => ( <Table.Row $hideHeader={rowIndex % 2 === 0} key={rowIndex}> <Table.RowHeader> <Flex $justifyContent="center" $flexDirection="column" $height="100%"> Row {rowIndex} Header </Flex> </Table.RowHeader> {Array.from({ length: 5 }).map((_, cellIndex) => ( <Table.RowCell key={cellIndex} $paddingX={2} $paddingY={4}> Row {rowIndex} Cell {cellIndex} </Table.RowCell> ))} </Table.Row> ))} </Table> </Container>
Subcomponents
| Subcomponent | Description |
Table.Header | Container for a row of column header cells |
Table.HeaderCell | Individual column header cells containing header information for each column |
Table.CollapsedHeader | Optional container for a row of column header cells. Replaces the Table.Header on scroll |
Table.Row | Container for a row of cells |
Table.RowHeader | Header cell for the following row |
Table.RowCell | Individual cells |
Table.Group | Groups multiple rows into an accordion |
Table.Switch | Table configuration switch |
Variants
Collapsed Header Table
The below example shows how to use an alternative header row:
<Container $size="10-col"> <Table $tableId="example-collapsed-header" $hideRowHeaders $stickyTop={80} $scrollableRegionTabIndex tableAriaLabel="Comparison Example 8"> <Table.Header> {Array.from({ length: 2 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $marginTop={4} $flexDirection="column" $justifyContent="flex-start" $alignItems="center" > <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Column Header {index + 1} </Text> <Image alt="" role="presentation" src="https://placehold.co/256x144" order="1" $width="100%" $aspectRatio="16 / 9" $marginBottom={3} /> </Flex> </Table.HeaderCell> ))} </Table.Header> <Table.CollapsedHeader> {Array.from({ length: 2 }).map((_, index) => ( <Table.HeaderCell $paddingX={2} $paddingY={4} key={index}> <Flex $marginTop={4} $flexDirection="column" $justifyContent="flex-start" $alignItems="center" > <Text $order="2" $fontSize={{ xs: 'display-7', lg: 'display-6' }}> Collapsed Header {index + 1} </Text> </Flex> </Table.HeaderCell> ))} </Table.CollapsedHeader> {Array.from({ length: 4 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader>Row Header {rowIndex + 1}</Table.RowHeader> {Array.from({ length: 2 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Duis aute irure dolor in reprehenderit. Excepteur sint occaecat cupidatat non proident. Sed ut perspiciatis unde omnis iste natus.{' '} </Table.RowCell> ))} </Table.Row> ))} </Table> </Container>
Row Group Table
Breaking Change in v12:
The Table.Group structure has changed in UI Core v12.0.0. The API for Table.Group has been updated,
and you must now use Table.GroupHeader and Table.GroupContent as children of Table.Group . Please
update your usage to match the new structure. Refer to the documentation and examples for the correct
usage.
The Table.Group component allows you to create collapsible row groups within the table. Each group can have a title and can be toggled open or closed. This is useful for organizing related rows together, such as grouping by categories or sections.
A Table.Group can take three props
| Prop | Type | Default | Description |
$isOpen | boolean | undefined | Set the initial state of the group. |
onOpen | function | undefined | Callback which is triggered when group is opened. |
onClose | function | undefined | Callback which is triggered when group is closed. |
| Subcomponent | Description |
Table.GroupHeader | The header for a row group. |
Table.GroupContent | The content container for a row group. Place your grouped Table.Row components inside this element. |
The below example shows a table with row groups:
<Container $size="10-col"> <Table $stickyTop={80} $tableId="example-row-group" tableAriaLabel="Comparison Example 9"> <Table.Switch /> <Table.Header> {Array.from({ length: 3 }).map((_, index) => ( <Table.HeaderCell key={index}>Column Header {index + 1}</Table.HeaderCell> ))} </Table.Header> <Table.Row> <Table.RowHeader>Row Header 1</Table.RowHeader> {Array.from({ length: 3 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Row 1 Cell {colIndex + 1} </Table.RowCell> ))} </Table.Row> {Array.from({ length: 2 }).map((_, groupIndex) => ( <Table.Group key={groupIndex} id={`group-${groupIndex}`} $isOpen={groupIndex === 1}> <Table.GroupHeader>Group Header {groupIndex + 1}</Table.GroupHeader> <Table.GroupContent> {Array.from({ length: 3 }).map((_, rowIndex) => ( <Table.Row key={rowIndex}> <Table.RowHeader> Group Row {groupIndex + 1}.{rowIndex + 1} </Table.RowHeader> {Array.from({ length: 3 }).map((_, colIndex) => ( <Table.RowCell key={colIndex}> Group {groupIndex + 1} Row {rowIndex + 1} Cell {colIndex + 1} </Table.RowCell> ))} </Table.Row> ))} </Table.GroupContent> </Table.Group> ))} </Table> </Container>
Callbacks
onSwitchChange
The onSwitchChange callback will return the state of the switch as a boolean.
() => { function Example(){ const [switchState, setSwitchState] = React.useState(false); const toggleSwitchState = e => { setSwitchState(e); }; return ( <> <Text $marginBottom={4}>Switch state: {switchState.toString()}</Text> <Table $tableId="example-switch-callback" $stickyTop={80} onSwitchChange={toggleSwitchState} tableAriaLabel="Comparison Example 10"> <Table.Switch /> <Table.Header> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 1</Text> </Table.HeaderCell> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 2</Text> </Table.HeaderCell> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 3</Text> </Table.HeaderCell> </Table.Header> <Table.Row> <Table.RowHeader $padding={2}> Attribute 1 </Table.RowHeader> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 1</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 2</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 3</Text> </Table.RowCell> </Table.Row> </Table> </> ); } return <Example /> }
onMoreToggle
The onMoreToggle callback will return the collapsed state of the table as a boolean.
() => { function Example(){ const [moreState, setMoreState] = React.useState(false); const toggleMoreState = e => { setMoreState(e); }; return ( <> <Text $marginBottom={4}>Collapsed state: {moreState.toString()}</Text> <Table $tableId="example-more-toggle" $stickyTop={80} onMoreToggle={toggleMoreState} $maxRows={1} tableAriaLabel="Comparison Example 11"> <Table.Header> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 1</Text> </Table.HeaderCell> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 2</Text> </Table.HeaderCell> <Table.HeaderCell $paddingX={2} $paddingY={4}> <Text $fontSize={{xs: 'display-6', lg: 'display-5'}} $textAlign="center" $width="100%">Option 3</Text> </Table.HeaderCell> </Table.Header> <Table.Row> <Table.RowHeader $padding={2}> Attribute 1 </Table.RowHeader> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 1</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 2</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 3</Text> </Table.RowCell> </Table.Row> <Table.Row> <Table.RowHeader $padding={2}> Attribute 2 </Table.RowHeader> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 1</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 2</Text> </Table.RowCell> <Table.RowCell $padding={2}> <Text $display="block" $textAlign="center">Value 3</Text> </Table.RowCell> </Table.Row> </Table> </> ); } return <Example /> }
System Modifiers
The Table component accepts props applied using the following system modifiers:
The HeaderCell and RowCell subcomponents also accepts props applied using the following system modifiers:
Translatable Fields
The Table supports translation of the following fields:
| Translation | Description |
table.roleDescription | Controls the roleDescription. (See MDN) |
table.paginationTitle.label | Controls the descriptive text for the pagination label |
table.previous.label | Controls the descriptive text for the previous button action |
table.next.label | Controls the descriptive text for the next button action |
table.switchOn.label | Controls the descriptive text for the switch on label action |
table.switchOff.label | Controls the descriptive text for the switch off label action |
table.switchTitle.label | Controls the text for the switch action |
table.showMore.label | Controls the text for the show more action |
table.showLess.label | Controls the text for the show less action |
For more on translatable fields, view the useTranslation docs here: useTranslation