Tabs

Tabs switch between views of the same content, such as Overview, Specs, and Reviews on one product. Primary tabs organize a page's main sections and secondary tabs divide one section further, so the style sets the hierarchy a reader sees. A bar that outgrows its space scrolls with an edge shadow, so you write no overflow handling. Press the tabs below to compare both styles.

pianoKeyboard music_noteGuitar albumDrums Overview Specs Reviews todayDay calendar_view_weekWeek Inbox Sent Drafts restaurantSoup ecoSalad icecreamDessert Ingredients Steps imagePhotos movieVideos photo_albumAlbums Ratings Photos Questions light_modeLight dark_modeDark

Reference docs⁠

BlazorReactWebOverviewExamplesAPIStylingAccessibility

Choosing navigation⁠

Two components show a row of destinations to choose from. Each suits a different job:

  • Navigation: moving between the app's top-level destinations, as a bar, rail, or drawer, such as Home, Search, Library.
  • Tabs: switching between views of the same content, such as Overview, Specs, Reviews on one product.

Two jobs belong elsewhere. Moving through pages of results is a paginator, and a list of commands is a menu.

Choosing a variant⁠

Two kinds of tab set how many levels a bar shows:

  • Primary tab: the main sections of a content area, such as Overview, Specs, and Reviews on a product page. It stacks an icon above the label in a 64-pixel tab, or places it beside the label in a 48-pixel tab with inline-icon.
  • Secondary tab: the parts of one section, such as Newest and Top rated under Reviews. Its indicator spans the full width of the tab, and an icon sits beside its label.

Built in⁠

  • active-tab-index and activeTab set and read the selected tab from code.
  • auto-activate selects a tab as soon as an arrow key focuses it, instead of waiting for Enter or Space.
  • beforechange fires before the selection moves and can be canceled; change fires after.
  • Tabs that overflow the bar scroll sideways, with no scrolling code to write.
  • The arrow keys, Home, and End move focus between tabs, and Left and Right swap in right-to-left layouts.
  • The active indicator slides between tabs, and moves at once under reduced motion.
  • A tab sets has-icon and icon-only for itself from what it holds, so you leave both unset in a browser.

Using tabs well⁠

  • Put primary tabs at the top of a content area, use secondary tabs one level below, and stop at two levels.
  • Write each label in one or two words, and let it show in full instead of truncating it.
  • Give every tab in a bar an icon, or none of them.
  • Use tabs for the views of one content area, and a navigation to move between an app's top-level destinations.
  • Leave the active indicator in place: a change of color alone does not mark the selected tab.

These rules follow Material 3 Expressive's guidance for tabs, on material.io.

Tab styles⁠

A primary tab, cw-primary-tab, shows an icon above its label, or beside it with inline-icon. A secondary tab, cw-secondary-tab, shows a label alone under a full-width indicator. The Overview's Choosing a variant says when to use each.

Icons⁠

A tab's icon goes in its icon slot. A primary tab stacks it above the label, and inline-icon puts it beside the label, which gives a shorter tab that takes more width. A secondary tab always shows it beside the label. Use one style across a bar, and turn the switch to compare the two.

A tab sets has-icon and icon-only for itself from what it holds, so set them only when rendering on the server.

Icon only⁠

A tab with an icon and no label sizes itself to the icon. Give it an aria-label, as the demo does; Names and states says why.

Panels⁠

The tab bar sets active on the selected tab and fires change, and that is all: it neither shows nor hides content. Show the panel for the selected tab yourself, as this demo does from the index the tab bar is bound to.

Tie each tab to its panel for assistive technology with the attributes that Names and states lists, as the demo does.

Selected tab⁠

active-tab-index selects a tab by its position, counting from 0, and activeTab reads the selected tab. An index outside the range of tabs is ignored, and so is setting activeTab to null. If the selected tab is removed, the first tab is selected.

The first tab is selected when the bar renders and active-tab-index is unset. To start the bar on another tab, set active on that tab and let the bar move it after that. selected is a deprecated alias of active.

Automatic activation⁠

By default the arrow keys move focus and Enter or Space selects the focused tab, so a reader can scan the labels without loading each view. auto-activate makes the arrow keys select as well as move focus. Turn it on only when showing a view is instant and cheap.

Change events⁠

beforechange fires first, when a click, a key press, or an assignment to active-tab-index is about to move the selection, and detail.activeTabIndex names the tab it is moving to. Call preventDefault() on it to leave the selection where it is. change fires after the move and cannot be canceled; it is the event a binding of active-tab-index follows, so the demo below binds the index rather than listening for it.

The automatic selection of the first tab raises neither event.

tabs.addEventListener("beforechange", (event) => {
  if (hasUnsavedEdits) {
    event.preventDefault();
  }
});
content_copy

Overflow⁠

When the tabs do not fit, the bar scrolls sideways and shows an edge shadow where more tabs lie beyond. There is nothing to configure. Keep the labels short enough that the most important tabs are visible without scrolling.

Two levels⁠

Primary tabs sit at the top of a content area and secondary tabs subdivide one of its sections. Use one tab bar for each level, and show the secondary bar only inside the panel that owns it. Stop at two levels.

Tabs Primary tab Secondary tab

Properties⁠

PropertyAttributeTypeDescription
activeTabTabThe currently selected tab, null only when there are no tab children. See Selected tab.
activeTabIndexactive-tab-indexnumberThe currently selected tab's index, or -1 when no tab is selected. Assigning an index outside the tabs is ignored. See Selected tab, Change events.
autoActivateauto-activatebooleandefault falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.

Slots⁠

SlotDescription
(default)The cw-primary-tab and cw-secondary-tab elements to be shown in the tab bar.

Events⁠

EventTypeDetailDescription
beforechangeTabsBeforeChangeEventTabsBeforeChangeEventDetailFired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or activeTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
changeTabsChangeEventTabsChangeEventDetailFired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's activeTabIndex or activeTab provide the same information. It bubbles.

Canceling beforechange also suppresses change.

Props⁠

PropTypeDescription
activeTabthrough the refTabThe currently selected tab, null only when there are no tab children. See Selected tab.
activeTabIndexnumberThe currently selected tab's index, or -1 when no tab is selected. Assigning an index outside the tabs is ignored. See Selected tab, Change events.
autoActivatebooleandefault falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.

Children⁠

ChildDescription
childrenThe CrPrimaryTab and CrSecondaryTab elements to be shown in the tab bar.

Events⁠

PropArgumentDescription
onBeforeChangeTabsBeforeChangeEventFired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or activeTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
onChangeTabsChangeEventFired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's activeTabIndex or activeTab provide the same information. It bubbles. Its detail carries activeTabIndex.

Canceling beforechange also suppresses change.

Parameters⁠

ParameterTypeDescription
AutoActivatebool?default falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.
Valueint?—
ValueExpressionExpression<Func<int?>>?Gets or sets an expression that identifies the bound value.

Render fragments⁠

ParameterTypeDescription
ChildContentRenderFragment?The CbPrimaryTab and CbSecondaryTab elements to be shown in the tab bar.

Event callbacks⁠

ParameterTypeDescription
OnBeforeChangeEventCallback<BeforeChangeEventArgs>Fired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or ActiveTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
OnChangeEventCallback<TabsChangeEventArgs>Fired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's ActiveTabIndex or activeTab provide the same information. It bubbles. Its detail carries ActiveTabIndex.

Canceling beforechange also suppresses change.

Inputs⁠

InputTypeDescription
activeTabIndexnumberThe currently selected tab's index, or -1 when no tab is selected. Assigning an index outside the tabs is ignored. See Selected tab, Change events.
autoActivatebooleandefault falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.
valuenumber—

Content projection⁠

ContentDescription
content with no slotThe CaPrimaryTab and CaSecondaryTab elements to be shown in the tab bar.

Outputs⁠

OutputEventDescription
cwBeforeChangeTabsBeforeChangeEventFired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or activeTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
cwChangeTabsChangeEventFired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's activeTabIndex or activeTab provide the same information. It bubbles. Its detail carries activeTabIndex.

Canceling beforechange also suppresses change.

Props⁠

PropTypeDescription
activeTabIndexnumberThe currently selected tab's index, or -1 when no tab is selected. Assigning an index outside the tabs is ignored. See Selected tab, Change events.
autoActivatebooleandefault falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.

Slots⁠

SlotDescription
defaultThe CvPrimaryTab and CvSecondaryTab elements to be shown in the tab bar.

Events⁠

EventTypeDescription
beforeChangeTabsBeforeChangeEventFired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or activeTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
changeTabsChangeEventFired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's activeTabIndex or activeTab provide the same information. It bubbles. Its detail carries activeTabIndex.

Canceling beforechange also suppresses change.

Props⁠

PropTypeDescription
activeTabIndexnumberThe currently selected tab's index, or -1 when no tab is selected. Assigning an index outside the tabs is ignored. See Selected tab, Change events.
autoActivatebooleandefault falseWhether the arrow, Home and End keys select the tab they move focus to, rather than only focusing it. A click always selects. See Automatic activation.

Snippets⁠

SnippetDescription
childrenThe CsPrimaryTab and CsSecondaryTab elements to be shown in the tab bar.

Events⁠

PropTypeDescription
onBeforeChangeTabsBeforeChangeEventFired before the selected tab changes, whether a user interaction like a space/enter key or click, or an author assigning activeTab or activeTabIndex, would change it. It does not fire for the first selection, when no tab was selected before. The event's detail.activeTabIndex carries the index the selection is about to move to. Call preventDefault() to leave the selection where it is. It bubbles and is cancelable.
onChangeTabsChangeEventFired after the selected tab changed, whether a user interaction or an author moved it. It is not cancelable. The event's detail.activeTabIndex carries the newly selected index; the target's activeTabIndex or activeTab provide the same information. It bubbles. Its detail carries activeTabIndex.

Canceling beforechange also suppresses change.

Properties⁠

PropertyAttributeTypeDescription
activeactivebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconhas-iconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlyicon-onlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
inlineIconinline-iconbooleandefault falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
selectedselectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndextabindexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Slots⁠

SlotDescription
(default)The tab's label content.
iconThe icon to display in the tab, intended for a cw-icon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
inlineIconbooleandefault falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Children⁠

ChildDescription
childrenThe tab's label content.
a child with slot="icon"The icon to display in the tab, intended for a CrIcon or equivalent.

Parameters⁠

ParameterTypeDescription
Activebool?default falseWhether the tab is the selected one. See Selected tab.
HasIconbool?default falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
IconOnlybool?default falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
InlineIconbool?default falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
Selectedbool?default falseDeprecated alias of Active. Use Active. See Selected tab.
TabIndexint?The element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Render fragments⁠

Once you write Icon or any other fragment as a child element, the main content must go in an explicit <ChildContent> too; markup beside <Icon> is a compile error. Here ChildContent is: The tab's label content.

<CbPrimaryTab>
    <Icon><CbIcon>favorite</CbIcon></Icon>
    <ChildContent>Main content</ChildContent>
</CbPrimaryTab>
content_copy
ParameterTypeDescription
ChildContentRenderFragment?The tab's label content.
IconRenderFragment?The icon to display in the tab, intended for a CbIcon or equivalent.

Inputs⁠

InputTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
inlineIconbooleandefault falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Content projection⁠

ContentDescription
content with no slotThe tab's label content.
an element with slot="icon"The icon to display in the tab, intended for a CaIcon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
inlineIconbooleandefault falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Slots⁠

SlotDescription
defaultThe tab's label content.
iconThe icon to display in the tab, intended for a CvIcon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
inlineIconbooleandefault falsePuts the icon beside the label instead of above it, which makes the tab shorter. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Snippets⁠

SnippetDescription
childrenThe tab's label content.
iconSnippetThe icon to display in the tab, intended for a CsIcon or equivalent.

Properties⁠

PropertyAttributeTypeDescription
activeactivebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconhas-iconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlyicon-onlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
selectedselectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndextabindexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Slots⁠

SlotDescription
(default)The tab's label content.
iconThe icon to display in the tab, intended for a cw-icon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Children⁠

ChildDescription
childrenThe tab's label content.
a child with slot="icon"The icon to display in the tab, intended for a CrIcon or equivalent.

Parameters⁠

ParameterTypeDescription
Activebool?default falseWhether the tab is the selected one. See Selected tab.
HasIconbool?default falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
IconOnlybool?default falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
Selectedbool?default falseDeprecated alias of Active. Use Active. See Selected tab.
TabIndexint?The element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Render fragments⁠

Once you write Icon or any other fragment as a child element, the main content must go in an explicit <ChildContent> too; markup beside <Icon> is a compile error. Here ChildContent is: The tab's label content.

<CbSecondaryTab>
    <Icon><CbIcon>favorite</CbIcon></Icon>
    <ChildContent>Main content</ChildContent>
</CbSecondaryTab>
content_copy
ParameterTypeDescription
ChildContentRenderFragment?The tab's label content.
IconRenderFragment?The icon to display in the tab, intended for a CbIcon or equivalent.

Inputs⁠

InputTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Content projection⁠

ContentDescription
content with no slotThe tab's label content.
an element with slot="icon"The icon to display in the tab, intended for a CaIcon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Slots⁠

SlotDescription
defaultThe tab's label content.
iconThe icon to display in the tab, intended for a CvIcon or equivalent.

Props⁠

PropTypeDescription
activebooleandefault falseWhether the tab is the selected one. See Selected tab.
hasIconbooleandefault falseWhether an icon is present. Set from the icon slot in a browser; set it by hand only in server rendering. See Icons.
iconOnlybooleandefault falseWhether the tab has no label and only an icon. Set from the default slot in a browser; set it by hand only in server rendering. See Icons.
selectedbooleandefault falseDeprecated alias of active. Use active. See Selected tab.
tabIndexnumberThe element's position in the tab order: 0 puts it in the order, and -1 leaves it out while script can still focus it. A group, such as a set of tabs, manages it for its members.

A tabs strip sets active when a tab is clicked or activated from the keyboard.

Snippets⁠

SnippetDescription
childrenThe tab's label content.
iconSnippetThe icon to display in the tab, intended for a CsIcon or equivalent.
Primary tab Secondary tab

A value is what the token defaults to; selecting it copies the token's name.

Tokens⁠

TokenValue
--ct-primary-tab-active-focus-icon-color--ct-sys-color-primary --ct-primary-tab-active-focus-icon-color
--ct-primary-tab-active-focus-label-text-color--ct-sys-color-primary --ct-primary-tab-active-focus-label-text-color
--ct-primary-tab-active-hover-icon-color--ct-sys-color-primary --ct-primary-tab-active-hover-icon-color
--ct-primary-tab-active-hover-label-text-color--ct-sys-color-primary --ct-primary-tab-active-hover-label-text-color
--ct-primary-tab-active-hover-state-layer-color--ct-sys-color-primary --ct-primary-tab-active-hover-state-layer-color
--ct-primary-tab-active-hover-state-layer-opacity--ct-sys-state-hover-state-layer-opacity --ct-primary-tab-active-hover-state-layer-opacity
--ct-primary-tab-active-icon-color--ct-sys-color-primary --ct-primary-tab-active-icon-color
--ct-primary-tab-active-indicator-color--ct-sys-color-primary --ct-primary-tab-active-indicator-color
--ct-primary-tab-active-indicator-height3px --ct-primary-tab-active-indicator-height
--ct-primary-tab-active-indicator-radius3px 3px 0px 0px --ct-primary-tab-active-indicator-radius
--ct-primary-tab-active-label-text-color--ct-sys-color-primary --ct-primary-tab-active-label-text-color
--ct-primary-tab-active-pressed-icon-color--ct-sys-color-primary --ct-primary-tab-active-pressed-icon-color
--ct-primary-tab-active-pressed-label-text-color--ct-sys-color-primary --ct-primary-tab-active-pressed-label-text-color
--ct-primary-tab-active-pressed-state-layer-color--ct-sys-color-primary --ct-primary-tab-active-pressed-state-layer-color
--ct-primary-tab-active-pressed-state-layer-opacity--ct-sys-state-pressed-state-layer-opacity --ct-primary-tab-active-pressed-state-layer-opacity
--ct-primary-tab-container-color--ct-sys-color-surface --ct-primary-tab-container-color
--ct-primary-tab-container-end-end-radiussame as --ct-primary-tab-container-radius --ct-primary-tab-container-end-end-radius
--ct-primary-tab-container-end-start-radiussame as --ct-primary-tab-container-radius --ct-primary-tab-container-end-start-radius
--ct-primary-tab-container-height48px --ct-primary-tab-container-height
--ct-primary-tab-container-radius--ct-sys-corner-radius-none --ct-primary-tab-container-radius
--ct-primary-tab-container-start-end-radiussame as --ct-primary-tab-container-radius --ct-primary-tab-container-start-end-radius
--ct-primary-tab-container-start-start-radiussame as --ct-primary-tab-container-radius --ct-primary-tab-container-start-start-radius
--ct-primary-tab-focus-icon-color--ct-sys-color-on-surface --ct-primary-tab-focus-icon-color
--ct-primary-tab-focus-label-text-color--ct-sys-color-on-surface --ct-primary-tab-focus-label-text-color
--ct-primary-tab-hover-icon-color--ct-sys-color-on-surface --ct-primary-tab-hover-icon-color
--ct-primary-tab-hover-label-text-color--ct-sys-color-on-surface --ct-primary-tab-hover-label-text-color
--ct-primary-tab-hover-state-layer-color--ct-sys-color-on-surface --ct-primary-tab-hover-state-layer-color
--ct-primary-tab-hover-state-layer-opacity--ct-sys-state-hover-state-layer-opacity --ct-primary-tab-hover-state-layer-opacity
--ct-primary-tab-icon-color--ct-sys-color-on-surface-variant --ct-primary-tab-icon-color
--ct-primary-tab-icon-size24px --ct-primary-tab-icon-size
--ct-primary-tab-label-text-color--ct-sys-color-on-surface-variant --ct-primary-tab-label-text-color
--ct-primary-tab-label-text-font--ct-sys-typescale-title-small-font --ct-primary-tab-label-text-font
--ct-primary-tab-label-text-line-height--ct-sys-typescale-title-small-line-height --ct-primary-tab-label-text-line-height
--ct-primary-tab-label-text-size--ct-sys-typescale-title-small-size --ct-primary-tab-label-text-size
--ct-primary-tab-label-text-weight--ct-sys-typescale-title-small-weight --ct-primary-tab-label-text-weight
--ct-primary-tab-pressed-icon-color--ct-sys-color-on-surface --ct-primary-tab-pressed-icon-color
--ct-primary-tab-pressed-label-text-color--ct-sys-color-on-surface --ct-primary-tab-pressed-label-text-color
--ct-primary-tab-pressed-state-layer-color--ct-sys-color-primary --ct-primary-tab-pressed-state-layer-color
--ct-primary-tab-pressed-state-layer-opacity--ct-sys-state-pressed-state-layer-opacity --ct-primary-tab-pressed-state-layer-opacity
--ct-primary-tab-with-icon-and-label-text-container-height64px --ct-primary-tab-with-icon-and-label-text-container-height

A value is what the token defaults to; selecting it copies the token's name.

Tokens⁠

TokenValue
--ct-secondary-tab-active-focus-icon-color--ct-sys-color-on-surface --ct-secondary-tab-active-focus-icon-color
--ct-secondary-tab-active-focus-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-active-focus-label-text-color
--ct-secondary-tab-active-hover-icon-color--ct-sys-color-on-surface --ct-secondary-tab-active-hover-icon-color
--ct-secondary-tab-active-hover-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-active-hover-label-text-color
--ct-secondary-tab-active-hover-state-layer-color--ct-sys-color-on-surface --ct-secondary-tab-active-hover-state-layer-color
--ct-secondary-tab-active-hover-state-layer-opacity--ct-sys-state-hover-state-layer-opacity --ct-secondary-tab-active-hover-state-layer-opacity
--ct-secondary-tab-active-icon-color--ct-sys-color-on-surface --ct-secondary-tab-active-icon-color
--ct-secondary-tab-active-indicator-color--ct-sys-color-primary --ct-secondary-tab-active-indicator-color
--ct-secondary-tab-active-indicator-height2px --ct-secondary-tab-active-indicator-height
--ct-secondary-tab-active-indicator-radius0 --ct-secondary-tab-active-indicator-radius
--ct-secondary-tab-active-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-active-label-text-color
--ct-secondary-tab-active-pressed-icon-color--ct-sys-color-on-surface --ct-secondary-tab-active-pressed-icon-color
--ct-secondary-tab-active-pressed-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-active-pressed-label-text-color
--ct-secondary-tab-active-pressed-state-layer-color--ct-sys-color-on-surface --ct-secondary-tab-active-pressed-state-layer-color
--ct-secondary-tab-active-pressed-state-layer-opacity--ct-sys-state-pressed-state-layer-opacity --ct-secondary-tab-active-pressed-state-layer-opacity
--ct-secondary-tab-container-color--ct-sys-color-surface --ct-secondary-tab-container-color
--ct-secondary-tab-container-end-end-radiussame as --ct-secondary-tab-container-radius --ct-secondary-tab-container-end-end-radius
--ct-secondary-tab-container-end-start-radiussame as --ct-secondary-tab-container-radius --ct-secondary-tab-container-end-start-radius
--ct-secondary-tab-container-height48px --ct-secondary-tab-container-height
--ct-secondary-tab-container-radius--ct-sys-corner-radius-none --ct-secondary-tab-container-radius
--ct-secondary-tab-container-start-end-radiussame as --ct-secondary-tab-container-radius --ct-secondary-tab-container-start-end-radius
--ct-secondary-tab-container-start-start-radiussame as --ct-secondary-tab-container-radius --ct-secondary-tab-container-start-start-radius
--ct-secondary-tab-focus-icon-color--ct-sys-color-on-surface --ct-secondary-tab-focus-icon-color
--ct-secondary-tab-focus-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-focus-label-text-color
--ct-secondary-tab-hover-icon-color--ct-sys-color-on-surface --ct-secondary-tab-hover-icon-color
--ct-secondary-tab-hover-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-hover-label-text-color
--ct-secondary-tab-hover-state-layer-color--ct-sys-color-on-surface --ct-secondary-tab-hover-state-layer-color
--ct-secondary-tab-hover-state-layer-opacity--ct-sys-state-hover-state-layer-opacity --ct-secondary-tab-hover-state-layer-opacity
--ct-secondary-tab-icon-color--ct-sys-color-on-surface-variant --ct-secondary-tab-icon-color
--ct-secondary-tab-icon-size24px --ct-secondary-tab-icon-size
--ct-secondary-tab-label-text-color--ct-sys-color-on-surface-variant --ct-secondary-tab-label-text-color
--ct-secondary-tab-label-text-font--ct-sys-typescale-title-small-font --ct-secondary-tab-label-text-font
--ct-secondary-tab-label-text-line-height--ct-sys-typescale-title-small-line-height --ct-secondary-tab-label-text-line-height
--ct-secondary-tab-label-text-size--ct-sys-typescale-title-small-size --ct-secondary-tab-label-text-size
--ct-secondary-tab-label-text-weight--ct-sys-typescale-title-small-weight --ct-secondary-tab-label-text-weight
--ct-secondary-tab-pressed-icon-color--ct-sys-color-on-surface --ct-secondary-tab-pressed-icon-color
--ct-secondary-tab-pressed-label-text-color--ct-sys-color-on-surface --ct-secondary-tab-pressed-label-text-color
--ct-secondary-tab-pressed-state-layer-color--ct-sys-color-on-surface --ct-secondary-tab-pressed-state-layer-color
--ct-secondary-tab-pressed-state-layer-opacity--ct-sys-state-pressed-state-layer-opacity --ct-secondary-tab-pressed-state-layer-opacity

Names and states⁠

The tab bar is announced as a horizontal tab list and each tab as a tab; both follow the WAI-ARIA Authoring Practices: Tabs pattern.

  • Give the bar an aria-label. It names the tab list.
  • A tab's accessible name is its label text. An icon-only tab has no name, because the icon is hidden from assistive technology, so give that tab its own aria-label.
  • The selected tab is announced as selected, and the others as not selected.
  • The tab bar does not link tabs to panels or hide anything. Give each tab aria-controls, give each panel role="tabpanel" and aria-labelledby, and hide the panels that are not selected, as Panels shows.
  • A tab has no disabled, href, or badge. For links between destinations, use Navigation.

Keyboard and focus⁠

Key Action
Tab Moves focus into the bar at the selected tab, and out of the bar from it.
Left Arrow Moves focus to the previous tab, wrapping from the first to the last. Moves to the next tab in a right-to-left layout.
Right Arrow Moves focus to the next tab, wrapping from the last to the first. Moves to the previous tab in a right-to-left layout.
Home Moves focus to the first tab.
End Moves focus to the last tab.
Enter or Space Selects the focused tab.

The component handles all of these keys itself. The arrow, Home, and End keys do nothing with fewer than two tabs, and Up Arrow and Down Arrow do nothing at any count.

  • The bar is a single Tab stop: Tab enters it at the selected tab, and returns there after focus leaves.
  • The arrow keys move focus without selecting. With auto-activate, a tab is selected as soon as it takes focus.
  • A focus ring appears inside the tab on keyboard focus, and not on a pointer click.

Pointer and touch⁠

  • By default a tab is 48 pixels tall, or 64 pixels with an icon stacked above its label, and the tabs share the width of the bar. Both are well above the 24 by 24 pixels that WCAG 2.5.8 asks for.
  • A press anywhere inside a tab, on its icon or its label, selects that tab.

Display settings⁠

  • In forced colors, the active indicator uses the system text color.
  • Under reduced motion, the active indicator moves between tabs at once instead of sliding, and the bar scrolls without smooth scrolling.
  • In a right-to-left layout, Left Arrow moves to the next tab and Right Arrow to the previous one.

Standards⁠

With its defaults, the tab bar meets three WCAG 2.2 success criteria:

  • 2.1.1 Keyboard (A)
  • 2.4.7 Focus Visible (AA)
  • 2.5.8 Target Size (Minimum) (AA)

Three more depend on how you use it:

  • 1.1.1 Non-text Content (A): give an icon-only tab an aria-label, and give a slotted image an alt.
  • 1.3.1 Info and Relationships (A): link each tab to its panel with aria-controls, role="tabpanel", and aria-labelledby.
  • 4.1.2 Name, Role, Value (A): give the bar an aria-label, and give an icon-only tab its own aria-label.