# How Dive Presents Image Analysis Results Using a TUI

> Explore how Dive presents image analysis results using a TUI. Dive's event-driven TUI renders interactive, color coded panes for layers file trees and efficiency metrics for efficient container image analysis.

- Repository: [Alex Goodman/dive](https://github.com/wagoodman/dive)
- Tags: internals
- Published: 2026-03-07

---

**Dive renders container image analysis through an event-driven, modular TUI built on gocui, where immutable analysis data flows from the image engine through a centralized bus to specialized view components that render interactive, color-coded panes for layers, file trees, and efficiency metrics.**

Dive, developed by wagoodman, is an open-source tool for exploring Docker and OCI image layers. After analyzing an image's filesystem and efficiency metrics, Dive presents the results in a full-screen, keyboard-driven text-user interface (TUI) that allows developers to interactively inspect layer-by-layer changes. Understanding how Dive transforms raw analysis data into this terminal interface reveals a clean separation between heavy computation and UI rendering.

## The Analysis-to-UI Data Pipeline

The journey from raw image data to rendered ASCII interface follows a strict pipeline that keeps the UI responsive by completing all heavy analysis before the TUI initializes.

### Generating Immutable Analysis Results

All filesystem analysis happens in [`dive/image/analysis.go`](https://github.com/wagoodman/dive/blob/main/dive/image/analysis.go), where the `image.Analyze` function walks the image layers and constructs an `image.Analysis` struct. This struct contains immutable snapshots of layer metadata, file-tree references (`RefTrees`), efficiency metrics, and the list of inefficient files. By completing this work upfront, Dive ensures the TUI only reads pre-computed data rather than performing I/O during rendering.

### Event Bus Communication

Once analysis completes, the CLI command emits the `ExploreAnalysis` event via `bus.ExploreAnalysis`, defined in [`internal/bus/event/event.go`](https://github.com/wagoodman/dive/blob/main/internal/bus/event/event.go). This event carries both the `Analysis` struct and a `ContentReader` for on-demand file extraction. The event-driven pattern decouples the analysis engine from the presentation layer.

### UI Initialization and gocui Setup

The `V1UI` struct in [`cmd/dive/cli/internal/ui/v1.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1.go) subscribes to the event bus during setup. When it receives the `ExploreAnalysis` event, the `Handle` method disables logging to prevent terminal interference and invokes `app.Run` from [`cmd/dive/cli/internal/ui/v1/app/app.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/app/app.go). This function initializes the **gocui** GUI framework, creates the master `controller`, and starts the main event loop.

### Controller and View Instantiation

The `controller.newController` function in [`cmd/dive/cli/internal/ui/v1/app/controller.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/app/controller.go) orchestrates the interface by calling `view.NewViews` from [`cmd/dive/cli/internal/ui/v1/view/views.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/views.go). This factory function instantiates all view objects—including `Layer`, `FileTree`, `Status`, `Filter`, `ImageDetails`, and `LayerDetails`—and wires them to their respective viewmodels seeded with the `cfg.Analysis` data.

## The TUI Layout Architecture

Dive's interface is composed of discrete view components arranged by a dedicated layout manager that translates terminal dimensions into pane coordinates.

### Layout Management Strategy

The [`cmd/dive/cli/internal/ui/v1/layout/manager.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/layout/manager.go) file defines how terminal real estate is partitioned: the **Layer** view occupies the bottom-left, the **FileTree** takes the right side, **LayerDetails** and **ImageDetails** stack in the top-right column, and the **Status** bar anchors the footer. This layout is dynamic, recalculating positions whenever the terminal resizes.

### View Components and ViewModels

Each pane is implemented as a separate view file (e.g., [`cmd/dive/cli/internal/ui/v1/view/layer.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/layer.go), [`cmd/dive/cli/internal/ui/v1/view/filetree.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/filetree.go)). Rather than accessing raw analysis data directly, views read from viewmodels like `viewmodel.LayerSetState` and `viewmodel.FileTreeViewModel`. These abstractions wrap the underlying data and provide formatting helpers for the ASCII interface.

## Rendering Image Analysis Data

The TUI presents four main content areas, each rendering specific aspects of the `image.Analysis` struct.

### Layer Navigation Pane

The Layer view, implemented in [`cmd/dive/cli/internal/ui/v1/view/layer.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/layer.go), renders a scrollable list of image layers using `viewmodel.NewLayerSetState`. Each line displays the layer ID, size, and a visual indicator of bytes added, removed, or modified. The view handles keyboard inputs for layer selection, notifying the controller via `controller.onLayerChange` to update dependent views.

### Filesystem Tree Visualization

The FileTree view in [`cmd/dive/cli/internal/ui/v1/view/filetree.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/filetree.go) displays hierarchical filesystem changes using a `viewmodel.FileTreeViewModel` backed by `filetree.FileTree` from the analysis. Files are color-coded by change type: added, removed, modified, or unchanged. The view supports real-time regex filtering and collapse/expand operations without re-querying the analysis data.

### Details and Efficiency Metrics

The **LayerDetails** and **ImageDetails** views (top-right column) render metadata from `cfg.Analysis`. LayerDetails shows the current layer's command, creation time, and size, while ImageDetails displays aggregate statistics including total size, efficiency percentage, and wasted bytes identified during analysis.

### Status Bar and Help System

The Status view in [`cmd/dive/cli/internal/ui/v1/view/status.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/view/status.go) assembles the footer bar by collecting `helpKeys` from each active view. It displays current keybindings and view context, providing a thin separator line that spans the terminal width.

## User Interaction and Real-Time Updates

### The Update Loop

When users navigate layers or modify filters, the controller's `UpdateAndRender` method in [`cmd/dive/cli/internal/ui/v1/app/controller.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/app/controller.go) propagates state changes to all viewmodels and invokes each visible view's `Render()` method. Because the underlying `image.Analysis` data is immutable, views can redraw efficiently without synchronization locks or data fetching delays.

### Keyboard Navigation and Configuration

All keyboard shortcuts are bound through gocui and defined in the `Preferences.KeyBindings` configuration. Users can customize these in `~/.config/dive/config.yaml`; for example, changing the "toggle view" shortcut from `Ctrl+Space` to `Ctrl+N` updates both the **Status** footer text and the underlying `app.controller.NextPane` binding without code changes.

## Practical Implementation Examples

### Analyzing an Image via CLI

The standard command-line flow demonstrates the complete pipeline:

```bash

# Analyze an image and launch the interactive TUI

dive my-image:latest

```

Under the hood, as simplified from [`cmd/dive/cli/internal/command/build.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/command/build.go):

```go
analysis, err := analyzer.Analyze(ctx, img)
if err != nil {
    // handle error
}

content := image.NewContentReader(img)
bus.ExploreAnalysis(*analysis, content)

```

### Embedding Dive's TUI in Another Application

The UI components can be embedded in external Go programs:

```go
package main

import (
    "context"
    "os"

    "github.com/wagoodman/dive/cmd/dive/cli/internal/ui/v1"
    "github.com/wagoodman/dive/dive/image"
    "github.com/wagoodman/dive/internal/bus"
)

func main() {
    // Resolve image (Docker, OCI, or tarball)
    img, _ := image.FromDocker("my-image:latest")
    
    // Run analysis
    analysis, _ := image.Analyze(context.Background(), img)
    
    // Configure UI
    cfg := v1.Config{
        Analysis:    *analysis,
        Content:     image.NewContentReader(img),
        Preferences: v1.DefaultPreferences(),
    }
    
    // Initialize and subscribe UI
    ui := v1.NewV1UI(v1.DefaultPreferences(), os.Stdout, false, 0)
    bus.Subscribe(ui)
    
    // Launch TUI via event
    bus.ExploreAnalysis(*analysis, cfg.Content)
}

```

### Customizing Key Bindings

Modify shortcuts via configuration file:

```yaml

# ~/.config/dive/config.yaml

keybindings:
  global:
    toggle-view:
      key: "Ctrl+N"

```

This change automatically updates the help text in the **Status** view and the gocui keybinding registration.

## Summary

- **Immutable Analysis First**: Dive completes all image analysis in [`dive/image/analysis.go`](https://github.com/wagoodman/dive/blob/main/dive/image/analysis.go) before the TUI initializes, ensuring the interface remains responsive by reading static data structures.
- **Event-Driven Activation**: The `ExploreAnalysis` event in [`internal/bus/event/event.go`](https://github.com/wagoodman/dive/blob/main/internal/bus/event/event.go) decouples analysis from presentation, triggering the UI creation in [`cmd/dive/cli/internal/ui/v1.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1.go).
- **Modular View Architecture**: The controller in [`cmd/dive/cli/internal/ui/v1/app/controller.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/app/controller.go) manages discrete view components (Layer, FileTree, Status) that render via gocui primitives.
- **Layout Management**: A dedicated layout manager in [`cmd/dive/cli/internal/ui/v1/layout/manager.go`](https://github.com/wagoodman/dive/blob/main/cmd/dive/cli/internal/ui/v1/layout/manager.go) positions four main panes: Layer, FileTree, Details columns, and Status footer.
- **Data Binding**: Views consume `viewmodel.LayerSetState` and `viewmodel.FileTreeViewModel` abstractions rather than raw analysis structs, enabling efficient re-rendering during navigation.

## Frequently Asked Questions

### How does Dive handle large images without freezing the terminal interface?

Dive separates computation from rendering by running `image.Analyze` to completion before launching the TUI. The analysis phase builds immutable structs containing all layer and filesystem data, which the UI then accesses as read-only references. Because the TUI never performs blocking I/O or heavy calculation during the render loop, it remains responsive even when displaying hundreds of layers or millions of files.

### Can the Dive TUI be used to analyze images programmatically without the CLI wrapper?

Yes. The `v1` package in `cmd/dive/cli/internal/ui/v1` exposes `NewV1UI` and `Config` structs that allow direct embedding. By creating a `v1.Config` with a pre-computed `image.Analysis` and `ContentReader`, then emitting the `bus.ExploreAnalysis` event, any Go program can launch the identical interface used by the command-line tool. This is useful for building custom image inspection tools that leverage Dive's visualization engine.

### What library does Dive use for terminal rendering and why?

Dive uses **gocui**, a minimalist console UI library for Go. gocui provides the primitive `*gocui.View` objects that Dive's view components (Layer, FileTree, etc.) use to draw ASCII panes. The library supports overlapping views, mouse-free keyboard navigation, and dynamic layout changes—features that align with Dive's need to display four simultaneous information panes in a responsive, terminal-native interface.

### How are keyboard shortcuts configured and displayed in the interface?

Keybindings are defined in the `Preferences.KeyBindings` configuration structure, typically loaded from `~/.config/dive/config.yaml`. During initialization, the controller binds these keys to gocui actions. Each view exports its relevant key descriptions via `helpKeys`, which the `Status` view aggregates into the footer help bar. When users modify bindings in the config file, both the underlying gocui registration and the displayed help text update automatically without code changes.