Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

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)

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

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 · Canonical source: Chapter 14
  • Navigation between screens (alternative to tabs): Navigation
Was this page helpful?