Skip to content

TabPanel

A tabbed interface for organizing content into switchable panels. TabPanel provides a complete solution with a tab bar, content area, and optional navigation controls.

Basic Usage

Create a tabbed interface using the fluent API. By default, TabPanel manages its own selection state (the first tab is selected initially):

csharp
using Hex1b;

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.TabPanel(tp => [
        tp.Tab("Overview", t => [
            t.Text("Welcome to Hex1b!"),
            t.Text(""),
            t.Text("This is the Overview tab content.")
        ]),
        tp.Tab("Settings", t => [
            t.Text("Application Settings"),
            t.Text(""),
            t.Text("Configure your preferences here.")
        ]),
        tp.Tab("Help", t => [
            t.Text("Documentation and Support"),
            t.Text(""),
            t.Text("Visit hex1b.dev for more information.")
        ])
    ]).Selector().Fill())
    .Build();

await terminal.RunAsync();

Selection with State

For controlled selection, use .Selected(condition) to declaratively set which tab is active based on your application state:

csharp
using Hex1b;

var state = new TabState();

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.Text($"Current tab: {state.SelectedTab}"),
        v.Text(""),
        v.TabPanel(tp => [
            tp.Tab("Documents", t => [
                t.Text("Your documents appear here")
            ]).Selected(state.SelectedTab == "Documents"),
            tp.Tab("Downloads", t => [
                t.Text("Your downloads appear here")
            ]).Selected(state.SelectedTab == "Downloads"),
            tp.Tab("Pictures", t => [
                t.Text("Your pictures appear here")
            ]).Selected(state.SelectedTab == "Pictures")
        ])
        .OnSelectionChanged(e => state.SelectedTab = e.SelectedTitle)
        .Selector()
        .Fill()
    ]))
    .Build();

await terminal.RunAsync();

class TabState
{
    public string SelectedTab { get; set; } = "Documents";
}

Key points:

  • Without .Selected(), TabPanel manages its own state (uncontrolled)
  • .Selected(condition) enables controlled selection based on state
  • When multiple tabs have .Selected(true), the first one wins
  • Use OnSelectionChanged to respond to user tab switches

Dynamic Tabs

Build tab-based interfaces where tabs are added and removed at runtime. This pattern is common for document editors, browser-like UIs, and multi-pane dashboards:

csharp
using Hex1b;

var state = new EditorState();

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithMouse()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.HStack(h => [
            h.Button("New Tab").OnClick(_ => state.AddTab()),
            h.Text($"  {state.Tabs.Count} tab(s) open")
        ]),
        v.Text(""),
        state.Tabs.Count == 0
            ? v.Text("No tabs open. Click 'New Tab' to add one.")
            : v.TabPanel(tp => state.Tabs.Select((tab, idx) =>
                tp.Tab(tab.Name, t => [
                    t.Text($"Content of {tab.Name}"),
                    t.Text(""),
                    t.Text($"Created at: {tab.CreatedAt:HH:mm:ss}")
                ])
                .Selected(idx == state.SelectedIndex)
                .RightActions(i => [
                    i.Icon("×").OnClick(_ => state.CloseTab(idx))
                ])
            ).ToArray())
            .OnSelectionChanged(e => state.SelectedIndex = e.SelectedIndex)
            .Selector()
            .Fill()
    ]))
    .Build();

await terminal.RunAsync();

class EditorState
{
    public List<TabInfo> Tabs { get; } = [];
    public int SelectedIndex { get; set; }
    private int _counter = 1;

    public void AddTab()
    {
        Tabs.Add(new TabInfo($"Tab {_counter++}", DateTime.Now));
        SelectedIndex = Tabs.Count - 1;
    }

    public void CloseTab(int index)
    {
        if (index >= 0 && index < Tabs.Count)
        {
            Tabs.RemoveAt(index);
            if (SelectedIndex >= Tabs.Count)
                SelectedIndex = Math.Max(0, Tabs.Count - 1);
        }
    }
}

record TabInfo(string Name, DateTime CreatedAt);

Features demonstrated:

  • Adding tabs dynamically with state
  • Close button using .RightActions()
  • Selection tracking with OnSelectionChanged
  • Conditional rendering when no tabs exist

Render Modes

TabPanel supports two visual styles:

Full Mode (Default)

Full mode displays visual separators above and below the tabs:

csharp
tp.TabPanel(...).Full()

Compact Mode

Compact mode shows only the tab row without separators, saving vertical space:

csharp

Tab Bar Features

Paging Arrows

When tabs overflow the available width, paging arrows (◀ ▶) appear automatically. Disable with:

csharp
tp.TabPanel(...).Paging(false)

Enable a dropdown menu (▼) for quick navigation to any tab:

csharp
tp.TabPanel(...).Selector()

Mouse Wheel Scrolling

When the mouse is over the tab bar, use the scroll wheel to page through tabs without changing the selection.

Tab Position

Tabs can appear at the top or bottom of the panel:

csharp
// Explicit positioning
tp.TabPanel(...).TabsOnTop()
tp.TabPanel(...).TabsOnBottom()

By default, TabPanel auto-detects position based on its location in a VStack:

  • First child → tabs on top
  • Last child → tabs on bottom
  • Otherwise → tabs on top

Tab Icons

Add icons to tabs for visual identification:

csharp
tp.Tab("Settings", t => [...]).WithIcon("⚙️")

Tab Actions

Add interactive action icons to tabs using .LeftActions() and .RightActions():

Close Button (Right Action)

csharp
tp.Tab("Document.cs", t => [...])
  .RightActions(a => [
      a.Icon("×").OnClick(_ => CloseDocument())
  ])

Pin Button (Left Action)

csharp
tp.Tab("Important.cs", t => [...])
  .LeftActions(a => [
      a.Icon("📌").OnClick(_ => TogglePin())
  ])

Multiple Actions

csharp
tp.Tab("Document.cs", t => [...])
  .LeftActions(a => [
      a.Icon("📌").OnClick(_ => TogglePin())
  ])
  .RightActions(a => [
      a.Icon("💾").OnClick(_ => Save()),
      a.Icon("×").OnClick(_ => Close())
  ])

Keyboard Navigation

KeyAction
Alt+RightSwitch to next tab
Alt+LeftSwitch to previous tab
TabMove focus to next focusable element
Shift+TabMove focus to previous focusable element
  • Navigator - Stack-based page navigation
  • Splitter - Resizable split views (often used with TabPanel)
  • Scroll - Scrollable content within tabs
  • List - Selectable item lists

Released under the MIT License.