Skip to content
lib components / Navigation

Tabs

Use tabs to organize related content into views that users can navigate between. Tabs enable users to switch between views while remaining within the same context on a page.

import { Tabs } from "@siteimprove/fancylib";

To use Tabs in PFG, use the FancyPFG TabWrapper component instead. This component provides access to Tabs and provides PFG routing functionality.

#Examples

The Tabs component consists of two parts: the tabs and their contents. These two parts can either be nested within the same container (container-level tabs) or separate containers (page-level tabs). Furthermore, the contents of a tab is by default unmounted when a different tab is selected. However, it's also possible to keep tab contents mounted. Lastly, tab labels can be accompanied by both icons and badges. All these options are showcased in the following sections.

#Default

This is the default implementation of the Tabs component. It doesn’t look great. This is partly because the content doesn't have any padding. This can be added by using a structure component, such as a Content component. However, the main reason is that Tabs are meant to be nested within a container, such as a Card. This is showcased in the next section.

This is the content for Tab 1.

const [selectedTab, setSelectedTab] = useState(0); return ( <Tabs selectedTab={selectedTab} onChange={setSelectedTab} tabs={[ { header: "Tab 1", content: <Paragraph>This is the content for Tab 1.</Paragraph>, }, { header: "Tab 2", content: <Paragraph>This is the content for Tab 2.</Paragraph>, }, ]} /> );

#Container-level

Here you seen an example of container-level tabs. That means the Tabs component’s two parts, the tabs and their contents, are both nested within the same container. In this case, a Card component. This container could also have been a Modal or Side Panel.

A Card with container-level tabs should always feature a Card.Header component. In other words, container-level tabs should not be used as a substitute for a Card.Header.

Card header

This is the content for Tab 1.

const [selectedTab, setSelectedTab] = useState(0); return ( <Card> <Card.Header>Card header</Card.Header> <Tabs selectedTab={selectedTab} onChange={setSelectedTab} tabs={[ { header: "Tab 1", content: ( <Content> <Paragraph>This is the content for Tab 1.</Paragraph> </Content> ), }, { header: "Tab 2", content: ( <Content> <Paragraph>This is the content for Tab 2.</Paragraph> </Content> ), }, ]} /> </Card> );

#Page-level

Here you see an example of page-level tabs. That means the Tabs component’s two parts, the tabs and their contents, are nested in different containers. The tabs are nested in a Card component by themselves. The tabs’ contents are nested in separate Cards.

This configuration enables you to trigger the visibility of multiple Cards on a page. Page-level tabs should only be used directly on a page — not in an overlay, such as a Modal or Side Panel.

Card A

This is the content for Card A belonging to Tab 1.

Card B

This is the content for Card B belonging to Tab 1.

const [selectedTab, setSelectedTab] = useState(0); const [pageTabs, tabContent] = useTabSet({ selectedTab, onChange: setSelectedTab, tabs: [ { header: "Tab 1", content: ( <> <Card> <Card.Header>Card A</Card.Header> <Content> <Paragraph>This is the content for Card A belonging to Tab 1.</Paragraph> </Content> </Card> <Card> <Card.Header>Card B</Card.Header> <Content> <Paragraph>This is the content for Card B belonging to Tab 1.</Paragraph> </Content> </Card> </> ), }, { header: "Tab 2", content: ( <> <Card> <Card.Header>Card C</Card.Header> <Content> <Paragraph>This is the content for Card C belonging to Tab 2.</Paragraph> </Content> </Card> <Card> <Card.Header>Card D</Card.Header> <Content> <Paragraph>This is the content for Card D belonging to Tab 2.</Paragraph> </Content> </Card> </> ), }, ], }); return ( <> <Card>{pageTabs}</Card> {tabContent} </> );

#Mounted

The contents of a tab is by default unmounted when a different tab is selected. However, it's also possible to keep tab contents mounted . This is done by setting the keepTabsMounted property to true, as shown in the example below.

Card A

This Card belongs to Tab 1 and will remain mounted when you select Tab 2.

const [selectedTab, setSelectedTab] = useState(0); const [pageTabs, tabContent] = useTabSet({ selectedTab, keepTabsMounted: true, onChange: setSelectedTab, tabs: [ { header: "Tab 1", content: ( <> <Card> <Card.Header>Card A</Card.Header> <Content> <Paragraph> This Card belongs to Tab 1 and will remain mounted when you select Tab 2. </Paragraph> </Content> </Card> </> ), }, { header: "Tab 2", content: ( <> <Card> <Card.Header>Card B</Card.Header> <Content> <Paragraph> This Card belongs to Tab 2 and will remain mounted when you select Tab 1. </Paragraph> </Content> </Card> </> ), }, ], }); return ( <> <Card>{pageTabs}</Card> {tabContent} </> );

#Tabs with data-observe-keys

If you want to track the activity of the individual tabs, you can assign a data-observe-key to each tab, as shown in the example below.

This is the content for Tab 1.

const [selectedTab, setSelectedTab] = useState(0); return ( <Tabs selectedTab={selectedTab} onChange={setSelectedTab} tabs={[ { header: "Tab 1", content: <Paragraph>This is the content for Tab 1.</Paragraph>, "data-observe-key": "tab-1", }, { header: "Tab 2", content: <Paragraph>This is the content for Tab 2.</Paragraph>, "data-observe-key": "tab-2", }, ]} /> );

#Badges

Here you seen an example of how to add badges to tabs. Badges are used to indicate the status or category of a UI element. Place them to the right of tab labels and set their size prop to small.

Tasks

  • Task 1
  • Task 2
  • Task 3
const [selectedTab, setSelectedTab] = useState(0); return ( <Card> <Card.Header>Tasks</Card.Header> <Tabs selectedTab={selectedTab} onChange={setSelectedTab} tabs={[ { header: ( <> To do <Badge type="subtle" size="small" variant="light"> 3 </Badge> </> ), content: ( <Content> <Ul items={["Task 1", "Task 2", "Task 3"]} /> </Content> ), }, { header: ( <> Doing <Badge type="neutral" size="small" variant="light"> 2 </Badge> </> ), content: ( <Content> <Ul items={["Task 1", "Task 2"]} /> </Content> ), }, { header: ( <> Done <Badge type="positive" size="small" variant="light"> 5 </Badge> </> ), content: ( <Content> <Ul items={["Task 1", "Task 2", "Task 3", "Task 4", "Task 5"]} /> </Content> ), }, ]} /> </Card> );

#Icons

Here you seen an example of how to add icons to tabs. The icons should improve scannability or clarify the meaning of the tab labels. They should also always be placed to the left of tab labels.

Issues

  • Potential issue 1
  • Potential issue 2
  • Potential issue 3
  • Potential issue 4
  • Potential issue 5
const [selectedTab, setSelectedTab] = useState(0); return ( <Card> <Card.Header>Issues</Card.Header> <Tabs selectedTab={selectedTab} onChange={setSelectedTab} tabs={[ { header: ( <> <Icon> <IconPotentialIssue /> </Icon> Potential </> ), content: ( <Content> <Ul items={[ "Potential issue 1", "Potential issue 2", "Potential issue 3", "Potential issue 4", "Potential issue 5", ]} /> </Content> ), }, { header: ( <> <Icon> <IconResolvedIssue /> </Icon> Resolved </> ), content: ( <Content> <Ul items={[ "Resolved issue 1", "Resolved issue 2", "Resolved issue 3", "Resolved issue 4", "Resolved issue 5", ]} /> </Content> ), }, ]} /> </Card> );

#Properties

This is the content for Tab 1.

PropertyDescriptionDefinedValue
tabsRequired
object[]List of Tabs
selectedTabRequired
numberDefault selected tab
onInitOptional
functionExecute once tabs has been initialized
onChangeOptional
functionExecutes once tab has changed
onUnMountOptional
functionCallback that calls when tabs are unmounted, typically for routing control
keepTabsMountedOptional
booleanIf true, the tabs will remain mounted after another tab is selected
inStaticModeOptional
"disable" | "hide" | "show"Specifies how tabs look in the static mode. Defaults to `disable`.
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 tabs to organize related content into views that users can navigate between.
  • Use tabs to help users find specific content, while remaining within the same context on a page.
  • Make sure the first tab is the one that's selected by default.
  • Order the tabs in a way that helps users find the content they’re looking for.

#Container-level vs. page-level tabs

  • Nest the Tab component’s two parts, the tabs and their contents, within either the same container (container-level tabs) or separate containers (page-level tabs).
  • Nest container-level tabs in a Card, Modal, or Side Panel.
  • Always provide Cards containing container-level tabs with a Card.Header component.
  • Nest page-level tabs in Cards.
  • Make sure all Cards placed beneath beneath page-level tabs are part of the contents of those tabs.
  • Try to avoid having multiple page-level tabs on a page. If unavoidable, nest them inside each other. That means some page-level tabs would function as the contents of other page-level tabs.
  • Choose page-level tabs when a use case can be solved both by container-level and page-level tabs.

#Icons

  • Add Icons to tab labels if that improves scannability or clarifies the labels' meaning.
  • Don’t use icons as a replacement for tab labels.
  • Don’t add icons to tab labels because of aesthetic reasons.
  • Place icons to the left of tab labels.
  • Either provide all of the tab labels with icons or none of them.

#Do not use when

  • Users don't need to stay within the same context. Instead, show the contents on different pages.
  • The number of tabs exceeds seven. Instead, consider combining the contents of some tabs or using multiple Tabs components.
  • Users need to switch between different views of the same data. Instead, use a Toggle Group
  • Users need to switch between different views within part of a Card. Instead, use a Toggle Group.
  • Users need to compare the contents of different tabs. Instead, use a Table or List table.
  • Users need to be guided through a series of steps in a flow. In the future, Fancy will offer a component for this use case. For now, you’ll need to create such a component yourself.

#Accessibility

Provide each tab with a unique and short label that clearly describes its content.

Explore detailed guidelines for this component: Accessibility Specifications

#Writing

  • Keep tab labels short — 1 to 3 words.
  • Make sure tab labels make sense within their context of use. For instance, labels such as "Active" and "Inactive" are clear when used on a page within the Policy product.

#Notable Changes

#Version 0.0.x

  • The CardTabbed and PageTabs components have been removed. Instead, there's now just a single component: Tabs. It can be configured to function as container-level or page-level tabs.