# How to create tabs (TabBar)

> Build a tab bar with per-tab content and react to tab changes.

Source: https://crossfly.dev/docs/cookbook/ui/abas — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# How to create tabs (TabBar)

**Purpose.** Organize content into pages switchable by a tab bar, with an
animated indicator and horizontal swipe between pages. Use it for top-level sections
(Home / Search / Profile) or to group related content without screen navigation.

## APIs used
- `new TabBar(TabBar.Tab... tabs)` — creates the bar from tab descriptors.
- `new TabBar.Tab(String text)` — text-only tab.
- `new TabBar.Tab(Icon icon)` — icon-only tab.
- `new TabBar.Tab(String text, Icon icon)` — tab with text and icon (text below the icon by default).
- `tabBar.setContent(Component... contents)` — sets the content of each tab; **the count must match the number of tabs** (otherwise it throws `IllegalArgumentException`).
- `tabBar.onTabChanged(TabBar.TabChangedListener listener)` — callback `onTabChanged(int index)` called when the active tab changes (by tap or swipe).
- `tabBar.setActiveTab(int index)` — switches the tab programmatically (animates the indicator and scrolls the content).
- `tabBar.getActiveIndex()` — index of the active tab.

## Complete example (copy, paste and run)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // Text-only tabs (self-contained, without relying on MaterialIcon glyphs)
      TabBar tabBar = new TabBar(
         new TabBar.Tab("Início"),
         new TabBar.Tab("Buscar"),
         new TabBar.Tab("Perfil"));

      // One content Component per tab, in the SAME order as the tabs
      Component telaInicio = new Label("Conteúdo: Início");
      Component telaBusca  = new Label("Conteúdo: Buscar");
      Component telaPerfil = new Label("Conteúdo: Perfil");

      tabBar.setContent(
         telaInicio,
         telaBusca,
         telaPerfil);

      tabBar.onTabChanged(index ->
      {
         // e.g.: load data on demand when the tab changes
         if (index == 1) carregarBusca();
      });

      add(LAY_FILL, tabBar);
   }

   private void carregarBusca()
   {
      // ... load Search tab data on demand ...
   }
}
```

> The snippet below shows **just the core**. To run it, place it in the `MainWindow` skeleton above.

## Minimal snippet
```java
TabBar tabBar = new TabBar(
   new TabBar.Tab("Início",  new MaterialIcon("")),
   new TabBar.Tab("Buscar",  new MaterialIcon("")),
   new TabBar.Tab("Perfil",  new MaterialIcon("")));

// One content Component per tab, in the SAME order as the tabs
tabBar.setContent(
   telaInicio,
   telaBusca,
   telaPerfil);

tabBar.onTabChanged(index ->
{
   // e.g.: load data on demand when the tab changes
   if (index == 1) carregarBusca();
});

add(tabBar);

// Switch tabs programmatically, if needed:
tabBar.setActiveTab(2);   // goes to "Profile"
```

> **Verification note (2026 code).** Confirmed in source: `TabBar(Tab...)`,
> `setContent(Component...)`, `onTabChanged(TabChangedListener)`, `setActiveTab(int)`.
> Behavior details: the tab bar slides horizontally when the tabs
> don't fit; swiping the content panel switches the active tab and moves the indicator; the
> tab placement (top vs. bottom) and the indicator style come from the active `Skin`'s
> `TabBarStyle` (e.g., `IOSSkin` puts the tabs at the bottom). If `setContent` is not
> called, the bar shows only the tabs with no content panel.

## Dig deeper
- Widget page: [TabBar](/docs/ui/widgets/tabbar) · Canonical source: Chapter 14
- Navigation between screens (alternative to tabs): [Navigation](/docs/ui/navigation)
