> Markdown version of [Tabs](https://vaadin.com/docs/latest/components/tabs). Section index: [llms.txt](https://vaadin.com/docs/latest/components/llms.txt)

# Tabs

Tabs are used to organize and group content into sections that the user can navigate.

Below is a simple example of tabs with labels:

**Lit** — `tabs-basic.ts`

```html
<vaadin-tabs>
  <vaadin-tab>Details</vaadin-tab>
  <vaadin-tab>Payment</vaadin-tab>
  <vaadin-tab>Shipping</vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsBasic.java`

```java
Tab details = new Tab("Details");
Tab payment = new Tab("Payment");
Tab shipping = new Tab("Shipping");

Tabs tabs = new Tabs(details, payment, shipping);
```

**React** — `tabs-basic.tsx`

```tsx
<Tabs>
  <Tab>Details</Tab>
  <Tab>Payment</Tab>
  <Tab>Shipping</Tab>
</Tabs>
```

Use Tabs when you want to allow in-place navigation within a certain part of the UI — that is to say, without having to load another page and instead of showing everything at once, or forcing the user to navigate to different views.

## <a id="tab-sheet"></a>Tab Sheet

Tabs are most conveniently used as part of a Tab Sheet that includes automatically switched content areas for each tab. Try clicking on the tabs in the example below. Notice how different text is displayed for each tab.

**Lit** — `tabsheet-basic.ts`

```html
<vaadin-tabsheet>
  <vaadin-tabs slot="tabs">
    <vaadin-tab id="dashboard-tab">Dashboard</vaadin-tab>
    <vaadin-tab id="payment-tab">Payment</vaadin-tab>
    <vaadin-tab id="shipping-tab">Shipping</vaadin-tab>
  </vaadin-tabs>

  <div tab="dashboard-tab">This is the Dashboard tab content</div>
  <div tab="payment-tab">This is the Payment tab content</div>
  <div tab="shipping-tab">This is the Shipping tab content</div>
</vaadin-tabsheet>
```

**Flow** — `TabSheetBasic.java`

```java
TabSheet tabSheet = new TabSheet();
tabSheet.add("Dashboard",
        new Div(new Text("This is the Dashboard tab content")));
tabSheet.add("Payment",
        new Div(new Text("This is the Payment tab content")));
tabSheet.add("Shipping",
        new Div(new Text("This is the Shipping tab content")));
add(tabSheet);
```

**React** — `tabsheet-basic.tsx`

```tsx
<TabSheet>
  <TabSheetTab label="Dashboard">
    <div>This is the Dashboard tab content</div>
  </TabSheetTab>

  <TabSheetTab label="Payment">
    <div>This is the Payment tab content</div>
  </TabSheetTab>

  <TabSheetTab label="Shipping">
    <div>This is the Shipping tab content</div>
  </TabSheetTab>
</TabSheet>
```

## <a id="states"></a>States

A Tab can be selected, unselected, or disabled.

**Lit** — `tabs-states.ts`

```html
<vaadin-tabs>
  <vaadin-tab>Selected</vaadin-tab>
  <vaadin-tab>Unselected</vaadin-tab>
  <vaadin-tab disabled>Disabled</vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsStates.java`

```java
Tab selected = new Tab("Selected");

Tab unselected = new Tab("Unselected");

Tab disabled = new Tab("Disabled");
disabled.setEnabled(false);

Tabs tabs = new Tabs(selected, unselected, disabled);
tabs.setSelectedTab(selected);
```

**React** — `tabs-states.tsx`

```tsx
<Tabs>
  <Tab>Selected</Tab>
  <Tab>Unselected</Tab>
  <Tab disabled>Disabled</Tab>
</Tabs>
```

You can disable a tab to mark it as unavailable. Disabled tabs can’t be focused and may be invisible to assistive technologies, such as screen readers.

Disabling can be preferable to hiding an element to prevent changes in layout when the element’s visibility changes. They can also make users aware of its existence even when unavailable.

### <a id="autoselect-flow"></a>Autoselect (Flow)

The first tab you add to Tabs or Tab Sheet is automatically selected. Similarly, when a selected tab is removed, the next available tab is automatically selected.

Autoselect is enabled by default, but you can disable this behavior if needed. Notice how none of the tabs in the example below are initially selected.

`TabsAutoselect.java`

```java
Tabs tabs = new Tabs();
tabs.setAutoselect(false);
```

When using autoselect and tab selection change listeners, you should add selection change listeners before adding any tabs if you want the listeners to trigger for the automatically selected tab.

## <a id="orientation-overflow"></a>Orientation & Overflow

Tabs support two different orientations: horizontal (default) and vertical, which is not currently supported in Tab Sheets. Base your choice of orientation on your particular use case and the available space. Both are explained below.

### <a id="horizontal"></a>Horizontal

Horizontal tabs may be easier for users to understand and associate with the content. They’re best suited for a small number of items, but provide scrolling on overflow. Try scrolling horizontally the tabs in the example here: you’ll see that there are more than the initially visible ones.

**Lit** — `tabs-horizontal.ts`

```html
<vaadin-tabs style="max-width: 100%; width: 400px;">
  <vaadin-tab>Analytics</vaadin-tab>
  <vaadin-tab>Customers</vaadin-tab>
  <vaadin-tab>Dashboards</vaadin-tab>
  <vaadin-tab>Documents</vaadin-tab>
  <vaadin-tab>Orders</vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsHorizontal.java`

```java
Tab analytics = new Tab("Analytics");
Tab customers = new Tab("Customers");
Tab dashboards = new Tab("Dashboards");
Tab documents = new Tab("Documents");
Tab orders = new Tab("Orders");

Tabs tabs = new Tabs(analytics, customers, dashboards, documents,
        orders);
tabs.setMaxWidth("100%");
tabs.setWidth("400px");
```

**React** — `tabs-horizontal.tsx`

```tsx
<Tabs style={{ maxWidth: '100%', width: '400px' }}>
  <Tab>Analytics</Tab>
  <Tab>Customers</Tab>
  <Tab>Dashboards</Tab>
  <Tab>Documents</Tab>
  <Tab>Orders</Tab>
</Tabs>
```

In horizontal orientation, scroll buttons are displayed by default to aid scrolling the tabs. These can be disabled by applying the `hide-scroll-buttons` theme variant. Notice in the example below that there’s no scrollbar or scroll buttons, but you can still scroll to reveal the overflowing tabs.

**Lit** — `tabs-hide-scroll-buttons.ts`

```html
<vaadin-tabs theme="hide-scroll-buttons" style="max-width: 100%; width: 400px;">
  <vaadin-tab>Analytics</vaadin-tab>
  <vaadin-tab>Customers</vaadin-tab>
  <vaadin-tab>Dashboards</vaadin-tab>
  <vaadin-tab>Documents</vaadin-tab>
  <vaadin-tab>Orders</vaadin-tab>
  <vaadin-tab>Products</vaadin-tab>
  <vaadin-tab>Tasks</vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsHideScrollButtons.java`

```java
Tab analytics = new Tab("Analytics");
Tab customers = new Tab("Customers");
Tab dashboards = new Tab("Dashboards");
Tab documents = new Tab("Documents");
Tab orders = new Tab("Orders");
Tab products = new Tab("Products");
Tab tasks = new Tab("Tasks");

Tabs tabs = new Tabs(analytics, customers, dashboards, documents,
        orders, products, tasks);
tabs.addThemeVariants(TabsVariant.HIDE_SCROLL_BUTTONS);
tabs.setMaxWidth("100%");
tabs.setWidth("400px");
```

**React** — `tabs-hide-scroll-buttons.tsx`

```tsx
<Tabs theme="hide-scroll-buttons" style={{ maxWidth: '100%', width: '400px' }}>
  <Tab>Analytics</Tab>
  <Tab>Customers</Tab>
  <Tab>Dashboards</Tab>
  <Tab>Documents</Tab>
  <Tab>Orders</Tab>
  <Tab>Products</Tab>
  <Tab>Tasks</Tab>
</Tabs>
```

> **Note:** Hiding the scroll buttons isn’t recommended, though, for UIs designed to be operated on non-touchscreen devices, as horizontal scrolling can be difficult without them.

### <a id="vertical"></a>Vertical

<!-- vale Vaadin.Wordiness = NO -->

Vertical tabs can sometimes be a better choice for a large number of items. It’s easier for the user to scan a vertical list of options. However, they may not always be easy to understand and associate with the content.

Vertical tabs also provide scrolling on overflow, but no scroll buttons. Incidentally, vertical orientation is not available for Tab Sheets.

<!-- vale Vaadin.Wordiness = YES -->

**Lit** — `tabs-vertical.ts`

```html
<vaadin-tabs orientation="vertical" style="height: 240px; width: 240px;">
  <vaadin-tab>Analytics</vaadin-tab>
  <vaadin-tab>Customers</vaadin-tab>
  <vaadin-tab>Dashboards</vaadin-tab>
  <vaadin-tab>Documents</vaadin-tab>
  <vaadin-tab>Orders</vaadin-tab>
  <vaadin-tab>Products</vaadin-tab>
  <vaadin-tab>Tasks</vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsVertical.java`

```java
Tab analytics = new Tab("Analytics");
Tab customers = new Tab("Customers");
Tab dashboards = new Tab("Dashboards");
Tab documents = new Tab("Documents");
Tab orders = new Tab("Orders");
Tab products = new Tab("Products");
Tab tasks = new Tab("Tasks");

Tabs tabs = new Tabs(analytics, customers, dashboards, documents,
        orders, products, tasks);
tabs.setOrientation(Tabs.Orientation.VERTICAL);
tabs.setHeight("240px");
tabs.setWidth("240px");
```

**React** — `tabs-vertical.tsx`

```tsx
<Tabs orientation="vertical" style={{ height: '240px', width: '240px' }}>
  <Tab>Analytics</Tab>
  <Tab>Customers</Tab>
  <Tab>Dashboards</Tab>
  <Tab>Documents</Tab>
  <Tab>Orders</Tab>
  <Tab>Products</Tab>
  <Tab>Tasks</Tab>
</Tabs>
```

## <a id="icons-other-tab-content"></a>Icons & Other Tab Content

In addition to text, tabs can contain icons and other content.

### <a id="icons"></a>Icons

Icons can be used to make tabs more prominent and easier to identify. They can be added next to the labels or above them (Lumo only). Generally, horizontal tabs work best with icons above the labels, as you can see in the example below.

**Lit** — `tabs-icons-horizontal.ts`

```html
<vaadin-tabs>
  <vaadin-tab theme="icon-on-top">
    <vaadin-icon icon="vaadin:user"></vaadin-icon>
    <span>Profile</span>
  </vaadin-tab>
  <vaadin-tab theme="icon-on-top">
    <vaadin-icon icon="vaadin:cog"></vaadin-icon>
    <span>Settings</span>
  </vaadin-tab>
  <vaadin-tab theme="icon-on-top">
    <vaadin-icon icon="vaadin:bell"></vaadin-icon>
    <span>Notifications</span>
  </vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsIconsHorizontal.java`

```java
Tab profile = new Tab(VaadinIcon.USER.create(), new Span("Profile"));
Tab settings = new Tab(VaadinIcon.COG.create(), new Span("Settings"));
Tab notifications = new Tab(VaadinIcon.BELL.create(),
        new Span("Notifications"));

// Set the icon on top
for (Tab tab : new Tab[] { profile, settings, notifications }) {
    tab.addThemeVariants(TabVariant.LUMO_ICON_ON_TOP);
}

Tabs tabs = new Tabs(profile, settings, notifications);
```

**React** — `tabs-icons-horizontal.tsx`

```tsx
<Tabs>
  <Tab theme="icon-on-top">
    <Icon icon="vaadin:user" />
    <span>Profile</span>
  </Tab>

  <Tab theme="icon-on-top">
    <Icon icon="vaadin:cog" />
    <span>Settings</span>
  </Tab>

  <Tab theme="icon-on-top">
    <Icon icon="vaadin:bell" />
    <span>Notifications</span>
  </Tab>
</Tabs>
```

Vertical tabs work best with icons next to labels, as you can see here.

**Lit** — `tabs-icons-vertical.ts`

```html
<vaadin-tabs orientation="vertical">
  <vaadin-tab>
    <vaadin-icon icon="vaadin:user"></vaadin-icon>
    <span>Profile</span>
  </vaadin-tab>
  <vaadin-tab>
    <vaadin-icon icon="vaadin:cog"></vaadin-icon>
    <span>Settings</span>
  </vaadin-tab>
  <vaadin-tab>
    <vaadin-icon icon="vaadin:bell"></vaadin-icon>
    <span>Notifications</span>
  </vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsIconsVertical.java`

```java
Tab profile = new Tab(VaadinIcon.USER.create(), new Span("Profile"));
Tab settings = new Tab(VaadinIcon.COG.create(), new Span("Settings"));
Tab notifications = new Tab(VaadinIcon.BELL.create(),
        new Span("Notifications"));

Tabs tabs = new Tabs(profile, settings, notifications);
tabs.setOrientation(Tabs.Orientation.VERTICAL);
```

**React** — `tabs-icons-vertical.tsx`

```tsx
<Tabs orientation="vertical">
  <Tab>
    <Icon icon="vaadin:user" />
    <span>Profile</span>
  </Tab>

  <Tab>
    <Icon icon="vaadin:cog" />
    <span>Settings</span>
  </Tab>

  <Tab>
    <Icon icon="vaadin:bell" />
    <span>Notifications</span>
  </Tab>
</Tabs>
```

Icon-only labels may sometimes be appealing, but they should provide a textual description using a [Tooltip](https://vaadin.com/docs/latest/components/tooltip.md), or an `aria-label` attribute for assistive technologies.

### <a id="other-elements"></a>Other Elements

Tabs can contain almost any UI elements. For instance, they can contain badges indicating the number of items per tab. In this example, there’s a gray box with a number to the right of each tab label, indicating the count for each.

**Lit** — `tabs-badges.ts`

```html
<vaadin-tabs>
  <vaadin-tab>
    <span>Open</span>
    <vaadin-badge number="24" theme="filled"></vaadin-badge>
  </vaadin-tab>
  <vaadin-tab>
    <span>Completed</span>
    <vaadin-badge number="49" theme="filled"></vaadin-badge>
  </vaadin-tab>
  <vaadin-tab>
    <span>Cancelled</span>
    <vaadin-badge number="5" theme="filled"></vaadin-badge>
  </vaadin-tab>
</vaadin-tabs>
```

**Flow** — `TabsBadges.java`

```java
Tab open = new Tab(new Span("Open"), createBadge(24));
Tab completed = new Tab(new Span("Completed"), createBadge(49));
Tab cancelled = new Tab(new Span("Cancelled"), createBadge(5));

Tabs tabs = new Tabs(open, completed, cancelled);
```

**React** — `tabs-badges.tsx`

```tsx
<Tabs>
  <Tab>
    <span>Open</span>
    <Badge number={24} theme="filled" style={badgeStyle} />
  </Tab>

  <Tab>
    <span>Completed</span>
    <Badge number={49} theme="filled" style={badgeStyle} />
  </Tab>

  <Tab>
    <span>Cancelled</span>
    <Badge number={5} theme="filled" style={badgeStyle} />
  </Tab>
</Tabs>
```

## <a id="prefix-suffix"></a>Prefix & Suffix

Custom content can be placed before or after the tabs in a Tab Sheet by placing that content in the `prefix` and `suffix` slots. Notice the additional content at both ends of the tab bar in this example.

**Lit** — `tabsheet-prefix-suffix.ts`

```html
<vaadin-button slot="prefix">Close all</vaadin-button>

<vaadin-button slot="suffix" theme="icon" aria-label="Add tab">
  <vaadin-icon icon="vaadin:plus"></vaadin-icon>
</vaadin-button>
```

**Flow** — `TabSheetPrefixSuffix.java`

```java
tabSheet.setPrefixComponent(new Button("Close all"));

Button plusButton = new Button(new Icon(VaadinIcon.PLUS));
plusButton.addThemeVariants(ButtonVariant.LUMO_ICON);
plusButton.setAriaLabel("Add tab");
tabSheet.setSuffixComponent(plusButton);
```

**React** — `tabsheet-prefix-suffix.tsx`

```tsx
<TabSheet>
  <Button slot="prefix">Close all</Button>

  <Button slot="suffix" theme="icon" aria-label="Add tab">
    <Icon icon="vaadin:plus" />
  </Button>

  <TabSheetTab label="Dashboard">
    <div>This is the Dashboard tab content</div>
  </TabSheetTab>

  <TabSheetTab label="Payment">
    <div>This is the Payment tab content</div>
  </TabSheetTab>

  <TabSheetTab label="Shipping">
    <div>This is the Shipping tab content</div>
  </TabSheetTab>
</TabSheet>
```

## <a id="focus-keyboard"></a>Focus & Keyboard

Tab focus is rendered differently when focused by the keyboard. Once a tab is focused, arrow keys can be used to move the focus between tabs. Try pressing `Enter` or `Space` to select the tab on which to focus — or click on one tab with your mouse. Then use the left and right arrow keys to shift the focus.

**Lit** — `tabs-focus-ring.ts`

```typescript
<vaadin-tabs>
  <vaadin-tab focus-ring>Details</vaadin-tab>
  <vaadin-tab>Payment</vaadin-tab>
  <vaadin-tab>Shipping</vaadin-tab>
</vaadin-tabs>
```

**React** — `tabs-focus-ring.tsx`

```tsx
<Tabs>
  <Tab {...{ 'focus-ring': '' }}>Details</Tab>
  <Tab>Payment</Tab>
  <Tab>Shipping</Tab>
</Tabs>
```

## <a id="common-cases"></a>Common Cases

### <a id="content-switching-without-tab-sheet"></a>Content Switching without Tab Sheet

Using the integrated content areas in Tab Sheet is the easiest way to switch among the different content for each tab. Sometimes, such as when the tabs need to be separated structurally from their content areas, it may be necessary to use the stand-alone Tabs component and manually implement content switching.

Try clicking on each tab here. Notice how the text content changes depending on which you select.

**Lit** — `tabs-content.ts`

```typescript
<vaadin-tabs @selected-changed="${this.selectedChanged}">
  <vaadin-tab>Dashboard</vaadin-tab>
  <vaadin-tab>Payment</vaadin-tab>
  <vaadin-tab>Shipping</vaadin-tab>
</vaadin-tabs>

<vaadin-vertical-layout theme="padding">
  <p>${this.content}</p>
</vaadin-vertical-layout>
```

**Flow** — `TabsContent.java`

```java
details = new Tab("Details");
payment = new Tab("Payment");
shipping = new Tab("Shipping");

Tabs tabs = new Tabs(details, payment, shipping);
tabs.addSelectedChangeListener(
        event -> setContent(event.getSelectedTab()));
```

**React** — `tabs-content.tsx`

```tsx
<Tabs selected={value.value} onSelectedChanged={selectedChanged}>
  <Tab>Dashboard</Tab>
  <Tab>Payment</Tab>
  <Tab>Shipping</Tab>
</Tabs>

<VerticalLayout theme="padding">
  <p>{`This is the ${pages[value.value]} tab`}</p>
</VerticalLayout>
```

### <a id="lazy-initialization-of-tab-contents"></a>Lazy Initialization of Tab Contents

Sometimes it can be desirable to initialize the contents for a tab, lazily. That is to say, delay its initialization until the tab is selected. When you first click on one of the tabs below, you’ll notice a loading indicator, and a short delay until the tab content is displayed for each.

**Lit** — `tabsheet-lazy-initialization.ts`

```html
@state()
private visitedTabs = new Set<number>();

private selectedTabChanged(event: TabSheetSelectedChangedEvent) {
  this.visitedTabs = new Set([...this.visitedTabs, event.detail.value]);
}

protected override render() {
  return html`
    <vaadin-tabsheet @selected-changed=${this.selectedTabChanged}>
      <vaadin-tabs slot="tabs">
        <vaadin-tab id="dashboard-tab">Dashboard</vaadin-tab>
        <vaadin-tab id="payment-tab">Payment</vaadin-tab>
        <vaadin-tab id="shipping-tab">Shipping</vaadin-tab>
      </vaadin-tabs>

      ${
        this.visitedTabs.has(0)
          ? html`<div tab="dashboard-tab">This is the Dashboard tab content</div>`
          : ''
      }
      ${
        this.visitedTabs.has(1)
          ? html`<div tab="payment-tab">This is the Payment tab content</div>`
          : ''
      }
      ${
        this.visitedTabs.has(2)
          ? html`<div tab="shipping-tab">This is the Shipping tab content</div>`
          : ''
      }
    </vaadin-tabsheet>
  `;
}
```

**Flow** — `TabSheetLazyInitialization.java`

```java
tabSheet.add("Dashboard", new LazyComponent(
            () -> new Text("This is the Dashboard tab content")));

public class LazyComponent extends Div {
    public LazyComponent(
            SerializableSupplier<? extends Component> supplier) {
        addAttachListener(e -> {
            if (getElement().getChildCount() == 0) {
                add(supplier.get());
            }
        });
    }
}
```

**React** — `tabsheet-lazy-initialization.tsx`

```tsx
const visitedTabs = useSignal(new Set<number>([0]));

const selectedTabChanged = (event: TabSheetSelectedChangedEvent) => {
  visitedTabs.value = new Set([...visitedTabs.value, event.detail.value]);
};

return (
  <TabSheet onSelectedChanged={selectedTabChanged}>
    <TabSheetTab label="Dashboard">
      {visitedTabs.value.has(0) && <div>This is the Dashboard tab content</div>}
    </TabSheetTab>

    <TabSheetTab label="Payment">
      {visitedTabs.value.has(1) && <div>This is the Payment tab content</div>}
    </TabSheetTab>

    <TabSheetTab label="Shipping">
      {visitedTabs.value.has(2) && <div>This is the Shipping tab content</div>}
    </TabSheetTab>
  </TabSheet>
);
```

## <a id="related-components"></a>Related Components

| Component                                                           | Usage recommendations                                                                                    |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [Accordion](https://vaadin.com/docs/latest/components/accordion.md) | An accordion is a vertically stacked set of expandable panels.                                           |
| [Details](https://vaadin.com/docs/latest/components/details.md)     | The Details component is an expandable panel for showing and hiding content to make the UI less crowded. |

`D66CE920-3532-41D6-9EB3-C2B91F77CC40`
