The page navigation is complete. You may now navigate the page content as you wish.
Skip to main content

Allows users to move among different views within the same context and at the same level of hierarchy.

Tabs allow users to move between different views within the same context at the same level of hierarchy, e.g., UI vs. CLI, macOS vs. Windows vs. Linux, etc.

Usage

When to use

  • To move between different views within the same context.
Do

Use Tabs to break up content that is related to each other and is on the same hierarchical level.

Tab usage example

When not to use

Don’t

Don’t use Tabs for primary or secondary navigation.

Tab usage example

Don’t

Don’t use Tabs for sequential content.

Tab usage example

Tab best practices

Don’t

We don’t recommend using a singular Tab; instead consider using a heading.

Tabs with only one tab

Don’t

Don’t trigger a Tab change via an external action, such as a submit or next button. Tab changes should only be triggered by the Tab itself as that is the expected user interaction.

Tabs with external trigger

Sizes

Medium tabs are the default and work best in most contexts. Large tabs can be used to emphasize important sections, enhance clarity, and convey hierarchical distinction.

Tabs sizes example

Choosing sizes

Mixing different tab sizes is not recommended and can negatively impact the natural page hierarchy. Choose tab sizes intentionally and align them with the hierarchical importance of the information they represent.

We recommend structuring Tabs to reflect the intended hierarchy of the content they contain:

  • Large Tabs sit higher in the page hierarchy and can be used to express more important content, but should be used more sparingly.
  • Medium tabs are more adaptable in most scenarios and naturally occupy the same hierarchy as most page content.

Spacing

Contained

When the content area consists of a contained component (e.g., table, card, etc), we recommend:

  • 24px above tabs
  • 0px between the tabs and the content area
  • Top & right border-radius of the content area be set to 0
  • The content area should be flush on the left & right with the tabs

Tabs contained spacing example

Nested

We don’t recommend using a nested tab structure, but if you must, don’t go beyond 2 levels of nesting. If finding that you need to go beyond 2 levels of nesting, consider using another navigation pattern or re-evaluating the information architecture of your product.

When nesting tabs, regardless of if your content area consists of contained or non-contained components; we recommend:

  • 24px above each Tabs instance
  • 16px between each Tabs instance and its corresponding content area
  • Top & right border-radius of the content area be set to 0, if contained
  • Each content area should be left indented by 16px

Tabs nested spacing example

Not contained

When the content area does not consist of a contained component (ie. text block, form, etc), we recommend:

  • 24px above Tabs
  • 16px between the Tabs and the content area
  • The content area should be flush on the left & right with the Tabs, if only one level of tabs. If nested levels, see "Nested".

Tabs not contained spacing example

Overflow

Tabs will fill 100% of the parent container, unless explicitly set to something else. When there are too many tabs to fit within the TabList, a horizontal scrollbar will help the user navigation hidden tabs.

Do you have a need for a more elegant overflow experience? Please let us know by submitting a request.

Content

Tabs should be short and concise, and a good indication of what content the user can expect to find within the TabPanel. They should not consist of full sentences.

How to use this component

<Hds::Tabs as |T|>
  <T.Tab>One</T.Tab>
  <T.Tab>Two</T.Tab>
  <T.Tab>Three</T.Tab>

  <T.Panel>Content 1</T.Panel>
  <T.Panel>Content 2</T.Panel>
  <T.Panel>Content 3</T.Panel>
</Hds::Tabs>

Size

Set @size to "large" to display the large Tabs variant. The default @size is "medium".

<Hds::Tabs @size="large" as |T|>
  <T.Tab>One</T.Tab>
  <T.Tab>Two</T.Tab>
  <T.Tab>Three</T.Tab>

  <T.Panel>Content 1</T.Panel>
  <T.Panel>Content 2</T.Panel>
  <T.Panel>Content 3</T.Panel>
</Hds::Tabs>

Pre-selecting a tab

While the first tab is selected by default, it is possible to customize the starting tab to display on page load in two different ways (depending on how the tabs' state is controlled/persisted).

Using @isSelected argument applied to one of the Tab elements

Declare which tab is selected when the component is first rendered by providing @isSelected argument to one of the Tab elements. From that moment on, the tab selection is controlled internally by the component.

<Hds::Tabs as |T|>
  <T.Tab>One</T.Tab>
  <T.Tab>Two</T.Tab>
  <T.Tab @isSelected={{true}}>Three</T.Tab>

  <T.Panel>Content 1</T.Panel>
  <T.Panel>Content 2</T.Panel>
  <T.Panel>Content 3, I am displayed on page load.</T.Panel>
</Hds::Tabs>

Using @selectedTabIndex argument applied to the Tabs component

If you want to control the internal "selected tab" state of the component, and possibly persist it in a query parameter, you need to provide a @selectedTabIndex argument to the main Tabs component. You also need to handle the change of state using the @onClickTab callback function, invoked whenever a user clicks/selects one of the tabs.

<Hds::Tabs @selectedTabIndex={{this.demoSelectedTab}} @onClickTab={{this.demoUpdateSelectedTabQueryParam}} as |T|>
  <T.Tab>One</T.Tab>
  <T.Tab>Two</T.Tab>
  <T.Tab >Three</T.Tab>

  <T.Panel>Content 1</T.Panel>
  <T.Panel>Content 2</T.Panel>
  <T.Panel>Content 3</T.Panel>
</Hds::Tabs>

Count and icon

<Hds::Tabs as |T|>
  <T.Tab @count="5">One</T.Tab>
  <T.Tab @icon="download">Two</T.Tab>
  <T.Tab>Three</T.Tab>

  <T.Panel>Content 1</T.Panel>
  <T.Panel>Content 2</T.Panel>
  <T.Panel>Content 3!</T.Panel>
</Hds::Tabs>

Call a function on tab click

Use the @onClickTab handler to pass in a custom function. For example, to store the active tab in the URL for persistence.

<Hds::Tabs @onClickTab={{this.logClickedTab}} as |T|>
  <T.Tab>One</T.Tab>
  <T.Tab>Two</T.Tab>
  <T.Tab>Three</T.Tab>

  <T.Panel>Content one</T.Panel>
  <T.Panel>Content two</T.Panel>
  <T.Panel>Content three</T.Panel>
</Hds::Tabs>

Nested tabs

We don’t recommend nesting tabs, but in case it’s necessary to implement such a feature a special code implementation needs to be used: the T.Panel needs to be exposed and its P.isVisible property needs to be provided to the nested tab using the @isParentVisible argument, so that when the parent visibility changes the nested tab can be initialized accordingly.

  <Hds::Tabs as |T|>
    <T.Tab>🐤 Birds</T.Tab>
    <T.Tab>🐠 Fishes</T.Tab>
    <T.Tab>🐙 Cephalopods</T.Tab>
    <T.Panel as |P|>
      <Hds::Tabs @isParentVisible={{P.isVisible}} as |NT|>
        <NT.Tab>🦜 Parrots</NT.Tab>
        <NT.Tab>🦅 Eagles</NT.Tab>
        <NT.Tab>🦉 Owls</NT.Tab>
        <NT.Panel><Doc::Placeholder @text="🦜 Content for Parrots" @height="50" /></NT.Panel>
        <NT.Panel><Doc::Placeholder @text="🦅 Content for Eagles" @height="50" /></NT.Panel>
        <NT.Panel><Doc::Placeholder @text="🦉 Content for Owls" @height="50" /></NT.Panel>
      </Hds::Tabs>
    </T.Panel>
    <T.Panel as |P|>
      <Hds::Tabs @isParentVisible={{P.isVisible}} as |NT|>
        <NT.Tab>🐬 Dolphins</NT.Tab>
        <NT.Tab>🦈 Sharks</NT.Tab>
        <NT.Panel><Doc::Placeholder @text="🐬 Content for Dolphins" @height="50" /></NT.Panel>
        <NT.Panel><Doc::Placeholder @text="🦈 Content for Sharks" @height="50" /></NT.Panel>
      </Hds::Tabs>
    </T.Panel>
    <T.Panel><Doc::Placeholder @text="🐙 Content for Cephalopods" @height="50" /></T.Panel>
  </Hds::Tabs>

Component API

Tabs

<[T].Tab> yielded component
Tabs::Tab yielded as contextual component (see below).
<[T].Panel> yielded component
Tabs::Panel yielded as contextual component (see below).
size enum
  • medium (default)
  • large
Sets the size of the Tabs.
onClickTab function
Callback function invoked when the one of the tabs is clicked, if provided. The function receives the DOM event and the tab's index (integer number) as arguments.
selectedTabIndex integer
This argument can be used to select the initial tab and control the component state bypassing its default internal state (usually via query parameters).
Notice: when the initial tab is set using this parameter instead of using the @isSelected argument on the Tab sub-component, the consumer is responsible for updating the index whenever the user clicks on one of the tabs (via onClickTab).
isParentVisible boolean
Special argument used to control nested tabs. See "How to use / Nested tabs" for details.
…attributes
This component supports use of ...attributes.

Contextual components

[T].Tab

The Tabs::Tab component, yielded as contextual component.

icon
Displays an optional icon in the tab. Accepts any icon name.
count string
Displays an optional count indicator in the tab. Accepts the text value that should go in Badge Count.
isSelected boolean
  • false (default)
Customizes the initial tab to display when the page is loaded. The first tab is selected on page load by default.
yield
Elements passed as children are yielded as inner content of a <button> HTML element.
…attributes
This component supports use of ...attributes.

[T].Panel

The Tabs::Panel component, yielded as contextual component.

yield
Elements passed as children are yielded as inner content of a <section> HTML element.
…attributes
This component supports use of ...attributes.

Anatomy

Tab container anatomy

Element Usage
Tab Required, two minimum
Content area Required, Tabs should never be used alone

Tab items anatomy

Element Usage
Selected Required, one is always selected
Bottom border Required
Icon Optional
Text Required
Count Optional
Indicator Required, displayed on selected Tab

States

Tab states

Conformance rating

Conformant

Animation

No indicator animation will be present for users that have enabled prefers-reduced-motion.

Keyboard navigation

Tab

Focus on a tab

Keyboard tab navigation example

Spacebar
Enter

Activate tab to display matching content area

Keyboard tab navigation example

Move between tabs

If focus is on the first tab, left arrow moves focus to the last tab. If focus is on the last tab, right arrow moves focus to the first tab.

Keyboard tab navigation example

Tab

Move to interactive element within the content area

Keyboard tab navigation example

Applicable WCAG Success Criteria

  • 1.3.1 Info and Relationships (Level A):
    Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
  • 1.3.2 Meaningful Sequence (Level A):
    When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.
  • 1.4.1 Use of Color (Level A):
    Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
  • 1.4.10 Reflow (Level AA):
    Content can be presented without loss of information or functionality, and without requiring scrolling in two dimensions.
  • 1.4.11 Non-text Contrast (Level AA):
    The visual presentation of the following have a contrast ratio of at least 3:1 against adjacent color(s): user interface components; graphical objects.
  • 1.4.12 Text Spacing (Level AA):
    No loss of content or functionality occurs by setting all of the following and by changing no other style property: line height set to 1.5; spacing following paragraphs set to at least 2x the font size; letter-spacing set at least 0.12x of the font size, word spacing set to at least 0.16 times the font size.
  • 1.4.13 Content on Hover or Focus (Level AA):
    Where receiving and then removing pointer hover or keyboard focus triggers additional content to become visible and then hidden, the following are true: dismissible, hoverable, persistent (see link).
  • 1.4.3 Minimum Contrast (Level AA):
    The visual presentation of text and images of text has a contrast ratio of at least 4.5:1
  • 1.4.4 Resize Text (Level AA):
    Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.
  • 2.1.1 Keyboard (Level A):
    All functionality of the content is operable through a keyboard interface.
  • 2.1.2 No Keyboard Trap (Level A):
    If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface.
  • 2.4.6 Headings and Labels (Level AA):
    Headings and labels describe topic or purpose.
  • 2.4.7 Focus Visible (Level AA):
    Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.
  • 3.2.1 On Focus (Level A):
    When any user interface component receives focus, it does not initiate a change of context.
  • 4.1.2 Name, Role, Value (Level A):
    For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.

Support

If any accessibility issues have been found within this component, let us know by submitting an issue.

4.15.0

Updated

Aligned private class properties to follow a standardized notation


Related