# Architecture of the Interactive TUI in witr: How Tab Navigation Works

> Explore the witr TUI architecture built with Bubble Tea. Learn how the centralized MainModel manages four data views and enables seamless keyboard and mouse tab navigation.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: architecture
- Published: 2026-08-09

---

**The witr terminal user interface is built on the Bubble Tea framework and uses a centralized `MainModel` struct to manage four distinct data views, supporting both keyboard (1-4 keys) and mouse-driven tab navigation.**

The `witr` repository provides a system monitoring tool with an interactive terminal UI. Its architecture cleanly separates state management, rendering, and input handling using the Bubble Tea Elm architecture. This article examines the core components in `internal/tui` and explains exactly how users switch between Processes, Ports, Containers, and Locks tabs.

## Core Architecture of the witr TUI

The TUI implementation resides in the `internal/tui` package and follows the **Model-Update-View** pattern required by the Bubble Tea framework.

### The MainModel State Container

At the center of the architecture is the **`MainModel`** struct defined in [`internal/tui/model.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/model.go). This struct implements the Bubble Tea `Model` interface and maintains all UI state, including the current view state, active tab, focus state, and data tables for each resource type.

The model defines a **`tab`** enumeration that represents the four logical views: `tabProcesses`, `tabPorts`, `tabContainers`, and `tabLocks`. These constants control which data source is displayed and refreshed.

### View Rendering Pipeline

The **`View()`** method in [`internal/tui/view.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/view.go) acts as the rendering dispatcher. It inspects the current `state` field and delegates to specialized view functions like `viewList()`, `viewProcessDetail()`, or `viewContainerDetail()`.

When in the list state, **`viewList()`** assembles the screen by selecting the appropriate table based on `activeTab` and arranging it within a `lipgloss`-styled layout. The tab bar itself is rendered as a static string within the outer style, not as a separate interactive component.

### Event Handling with Update

The **`Update()`** method in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) processes all Bubble Tea messages, including keyboard input, mouse events, and window resizes. This is where tab navigation logic is implemented. The method handles key presses for tab switching, interprets mouse coordinates for tab bar clicks, and manages focus transitions between the main table and detail panes.

## How Tab Navigation Works in witr

Tab navigation in witr supports both keyboard shortcuts and mouse interactions, allowing users to switch between the four monitoring views seamlessly.

### Keyboard Navigation Using Number Keys

When the UI is in the list state (`stateList`) and no search input has focus, pressing the number keys `1` through `4` immediately switches to the corresponding tab. This logic is handled in the `handleKey` helper within [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go).

```go
case "1": m.activeTab = tabProcesses
case "2": m.activeTab = tabPorts
case "3": m.activeTab = tabContainers
case "4": m.activeTab = tabLocks

```

After updating `m.activeTab`, the system queues a refresh command specific to that tab—such as `m.refreshPorts()` or `m.refreshContainers()`—to ensure the data is current when the view renders.

### Mouse Interaction on the Tab Bar

The TUI interprets clicks on the first line of the terminal (Y-coordinate = 1) as tab selection events. The `Update()` function checks the X-coordinate of the click against pre-calculated column ranges for each tab label.

```go
if msg.Y == 1 && isClick {
    if msg.X >= 8 && msg.X < 22 {   // "1. Processes"
        m.activeTab = tabProcesses
    } else if msg.X >= 22 && msg.X < 32 { // "2. Ports"
        m.activeTab = tabPorts
    } // ... additional ranges for Containers and Locks
}

```

A mouse click also resets the focus to the main list by setting `m.listFocus = focusMain`, ensuring the user can immediately interact with the newly selected tab's data.

### Focus Management vs Tab Switching

It is important to distinguish between **tab navigation** and **focus navigation**. Pressing the **`Tab`** key does not change the active tab; instead, it toggles focus between the main data table and the side or detail pane. This allows users to navigate within a tab's interface without switching data views.

## State Transitions and Data Refresh

Changing `m.activeTab` does not alter the high-level `m.state`, which remains `stateList`. The next rendering cycle picks up the new tab value and displays the corresponding table. Each tab maintains its own data-fetching logic, such as `refreshProcesses()` or `refreshLocks()`, which is triggered immediately upon tab activation to populate the view with live system data.

## Key Source Files

Understanding the witr TUI architecture requires familiarity with these specific files:

- **[`internal/tui/model.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/model.go)** – Defines the `MainModel` struct, tab enumerations, and initial state setup.
- **[`internal/tui/view.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/view.go)** – Implements the rendering logic including `View()` and `viewList()` methods.
- **[`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go)** – Contains the `Update()` method and all input handling for keyboard and mouse events.
- **[`internal/tui/constants.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/constants.go)** – Stores layout constants including tab label strings and pane dimension ratios.

## Summary

- The **witr TUI** is implemented using the Bubble Tea framework in the `internal/tui` package.
- **Tab navigation** is controlled by the `activeTab` field in `MainModel`, supporting both keyboard (keys 1-4) and mouse clicks on the first line.
- The **`View()`** method dispatches rendering based on state, while **`Update()`** in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) handles all input events.
- Pressing **`Tab`** changes focus between UI elements but does not switch tabs.
- Each tab triggers its own **refresh command** (e.g., `refreshPorts()`) when activated to ensure data consistency.

## Frequently Asked Questions

### What framework does witr use for its TUI?

The witr project uses the **Bubble Tea** framework, which implements the Elm architecture pattern for Go terminal applications. This framework requires a `Model` struct with `Update()` and `View()` methods, which witr implements in the `internal/tui` package.

### How do I switch between tabs using the keyboard in witr?

Press the number keys **`1`**, **`2`**, **`3`**, or **`4`** when not actively editing a search field. These map directly to Processes, Ports, Containers, and Locks respectively, as implemented in the `handleKey` function within [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go).

### Why doesn't the Tab key change tabs in witr?

In witr, the **`Tab`** key toggles focus between the main data table and the side detail pane rather than switching tabs. This design choice allows users to navigate within a tab's interface before switching to a different data view using the number keys or mouse.

### How does witr detect which tab I clicked with the mouse?

The TUI checks if the click occurred on line 1 (the tab bar) and compares the X-coordinate against hardcoded ranges: 8-21 for Processes, 22-31 for Ports, and subsequent ranges for Containers and Locks. This logic is defined in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) alongside the keyboard handling code.