Underline Tabs
Underline Tabs are best used when switching between related content within a page.
<State initial={0}>
{([active, setActive]) => (
<TabGroup>
<Tab onClick={() => setActive(0)} active={active === 0}>All</Tab>
<Tab onClick={() => setActive(1)} active={active === 1}>Action Required</Tab>
<Tab onClick={() => setActive(2)} active={active === 2}>Insights</Tab>
<Tab onClick={() => setActive(3)} active={active === 3}>Settings</Tab>
</TabGroup>
)}
</State>
Underline tabs have a minimum width of 48px. The text and hover/active underline are always equal in width beyond this minimum.
With Badges
<TabGroup>
<Tab>Ready To Bill <Tag compact badge>127</Tag></Tab>
<Tab active>Action Required <Tag compact badge color="critical">25</Tag></Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
With Icons
<TabGroup>
<Tab active><Icon name="info" />Action Requested</Tab>
<Tab><Icon name="poll" /> Insights</Tab>
<Tab><Icon name="settings" /> Settings</Tab>
</TabGroup>
Tab Action
Tab actions, represented with a button or icon, can be represented as a tab itself. This can be useful for non-navigational actions related to the tab content such as adding a new tab.
<TabGroup
action={
<Button size="xsmall" primary>Call to Action</Button>
}
>
<style>{`
.icon-hov {
cursor: pointer;
}
.icon-hov:hover svg { fill: #2270ee; }
`}</style>
<Tab active>Action Requested</Tab>
<Tab> Insights</Tab>
</TabGroup>
<TabGroup
action={
<Icon name="add_circle" className="icon-hov fs-4 c-neutral-200" />
}
>
<style>{`
.icon-hov {
cursor: pointer;
}
.icon-hov:hover svg { fill: #2270ee; }
`}</style>
<Tab active>Action Requested</Tab>
<Tab> Insights</Tab>
</TabGroup>
Divided Tabs
A vertical pipe (|
) divider can be applied to both sides of an inactive tab. This is useful when there are many tabs on a page.
<State initial={0}>
{([active, setActive]) => (
<TabGroup
verticallyDivided
action={
<Button size="xsmall" primary>Call to Action</Button>
}
>
<Tab active={active == 0} onClick={() => setActive(0)}>All</Tab>
<Tab active={active == 1} onClick={() => setActive(1)}>Action Required</Tab>
<Tab active={active == 2} onClick={() => setActive(2)}>Insights</Tab>
<Tab active={active == 3} onClick={() => setActive(3)}>Action Required</Tab>
<Tab active={active == 4} onClick={() => setActive(4)}>Insights</Tab>
<Tab active={active == 5} onClick={() => setActive(5)}>Action Required</Tab>
</TabGroup>
)}
</State>
Fitted
<Card>
<Card.Section>
<TabGroup fitted divider={false}>
<Tab active><Icon name="info" />Action Requested</Tab>
<Tab><Icon name="poll" /> Insights</Tab>
<Tab ><Icon name="settings" /> Settings</Tab>
</TabGroup>
</Card.Section>
<Card.Section>
This card has supporting text below as a natural lead-in to additional content.
</Card.Section>
</Card>
Underline tabs can be fitted to a card. This pattern should only be used when there are 2 or 3 tabs.
Index Tabs
Index Tabs are best used when switching between tabs only affects content within the visual container its attached to. They can also be nested within an underline tab.
<TabGroup type="index">
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
Index tabs have a minimum width of 64px. The text and hover/active underline are always equal in width beyond this minimum.
With Badges
<TabGroup type="index">
<Tab>Ready To Bill <Tag compact badge>127</Tag></Tab>
<Tab active>Action Required <Tag compact badge color="critical">25</Tag></Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
With Icons
<TabGroup type="index">
<Tab active><Icon name="info" />Action Requested</Tab>
<Tab><Icon name="poll" /> Insights</Tab>
<Tab><Icon name="settings" /> Settings</Tab>
</TabGroup>
Fitted
<Card padding="thin">
<Card.Section>
<TabGroup type="index" fitted divider={false}>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab active>Settings</Tab>
</TabGroup>
</Card.Section>
<Card.Section>
Lorem ipsum dolar emit
</Card.Section>
</Card>
Max Width
Prevents a tab from exceeding 240px
in length. When combined with the fitted property, can make tabs stretch to the maximum width.
<TabGroup type="index" fitted maxWidth>
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
</TabGroup>
Left Aligned Tab Text
By default, text is centered within a tab.
<TabGroup type="index" fitted maxWidth textAlign="left">
<Tab active>John Doe</Tab>
<Tab>New Tab</Tab>
<Tab>Insights</Tab>
</TabGroup>
Divided Tabs
<State initial={0}>
{([active, setActive]) => (
<TabGroup
type="index"
verticallyDivided
action={
<Button size="xsmall" primary>Call to Action</Button>
}
>
<Tab active={active == 0} onClick={() => setActive(0)}>All</Tab>
<Tab active={active == 1} onClick={() => setActive(1)}>Action Required</Tab>
<Tab active={active == 2} onClick={() => setActive(2)}>Insights</Tab>
<Tab active={active == 3} onClick={() => setActive(3)}>Action Required</Tab>
<Tab active={active == 4} onClick={() => setActive(4)}>Insights</Tab>
<Tab active={active == 5} onClick={() => setActive(5)}>Action Required</Tab>
</TabGroup>
)}
</State>
With a Background
Tabs can take advantage of different background colors. The blue active state and dividers are removed, and inactive tabs switch text color.
<div className='box-demo'>
<TabGroup type="index" dark divider={false}>
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
<style>{`
.box-demo {
background: #8C9CA5;
padding: 20px 20px 0;
}
.tab-demo {
background: white;
width: calc(100% + 40px);
height: 40px;
margin: 0 -20px;
}
`}</style>
<div className='tab-demo' />
</div>
<div className='box-demo-2'>
<TabGroup type="index" dark verticallyDivided divider={false}>
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
<style>{`
.box-demo-2 {
background: #232323;
padding: 20px 20px 0;
}
.tab-demo {
background: white;
width: calc(100% + 40px);
height: 40px;
margin: 0 -20px;
}
`}</style>
<div className='tab-demo' />
</div>
Sizing
A taller variation of index tabs can be used for important navigation at the top of a page.
<TabGroup type="index" size="large">
<Tab active>Customers</Tab>
<Tab>Available Inventory</Tab>
<Tab>Analytics</Tab>
<Tab>Marketing</Tab>
</TabGroup>
Nested
If a page needs multiple levels of tabs, the index tab can be nested in a underline tab.
<div>
<TabGroup>
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
</TabGroup>
<br/>
<Card padding="thin">
<Card.Section>
<TabGroup type="index" divider={false}>
<Tab>SubTab 1</Tab>
<Tab>SubTab 2</Tab>
<Tab active>SubTab 3</Tab>
<Tab>SubTab 4</Tab>
<Tab>SubTab 5</Tab>
</TabGroup>
</Card.Section>
<Card.Section>
Lorem ipsum dolar emit
</Card.Section>
</Card>
</div>
Responsive
Tabs will scroll horizontally when overflowed.
<div>
<Headline className="m-b-3">Horizontal scrolling</Headline>
<TabGroup>
<Tab active>All</Tab>
<Tab>Action Required</Tab>
<Tab>Insights</Tab>
<Tab>Settings</Tab>
<Tab>More Items</Tab>
<Tab>Sixth Item</Tab>
</TabGroup>
<br/>
<Card padding="thin">
<Card.Section>
<TabGroup type="index" divider={false}>
<Tab>SubTab 1</Tab>
<Tab active>SubTab 2</Tab>
<Tab>SubTab 3</Tab>
<Tab>SubTab 4</Tab>
<Tab>SubTab 5</Tab>
<Tab>SubTab 6</Tab>
</TabGroup>
</Card.Section>
<Card.Section>
Lorem ipsum dolar emit
</Card.Section>
</Card>
</div>
Best Practices
- Avoid tab names over 2 words long.
- Should be used with 2–10 choices.
- Only 1 tab should be active at a time.
- Avoid the disabled state. If a user cannot use it, remove it from the tab.
- Content within a tab should be mutually exclusive from another tab's content.
- A visual divider should always be present. The default divider is a gray line.
- On tab switch, the
<title>
should change to reflect the new content.
- More UX practices: Tabs, Used Right (NN Group)
Importing
import { TabGroup, Tab } from '@servicetitan/design-system';