# Using ImGui's Table API for Complex Layouts: A Complete Technical Guide

> Master ImGui's table API for advanced UI layouts. Learn to build sophisticated interfaces with frozen panes, sortable headers, and nested tables. A complete technical guide.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-30

---

**ImGui's table API provides a state-driven grid system that replaces the deprecated Columns API with frozen panes, sortable headers, nested sub-tables, and column-spanning capabilities for building sophisticated UI layouts in Dear ImGui.**

The modern table system introduced in Dear ImGui (ocornut/imgui) version 1.80 offers a declarative alternative to manual coordinate calculations when constructing data-heavy interfaces. This API handles automatic layout, clipping, scrolling, and interaction through an interface defined primarily in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), allowing developers to build complex layouts ranging from simple property grids to nested configuration panels with minimal boilerplate.

## Core Table API Components

### Table Initialization and Lifecycle

Every table begins with `ImGui::BeginTable()` and ends with `ImGui::EndTable()`. According to the source code in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 886-907), `BeginTable` accepts a string ID, column count, optional flags for behavior customization, and sizing parameters:

```cpp
bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, 
                const ImVec2& outer_size = ImVec2(0,0), float inner_width = 0.0f)

```

The function returns **false** when the table is clipped outside the visible region, enabling early-out optimizations. This check occurs before any column data is processed, making it efficient for large datasets.

### Column Declaration and Configuration

Column properties are defined through `TableSetupColumn()`, as documented in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (lines 921-922):

```cpp
void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, 
                      float init_width_or_weight = 0.0f, ImGuiID user_data = 0)

```

This function establishes resizing policies, default sorting behavior, and visibility states. When combined with `TableHeadersRow()` (lines 924-925), it automatically generates a header row with context menus for column reordering and sorting.

### Row and Column Navigation

The API provides two navigation models. **Sequential access** uses `TableNextRow()` followed by `TableNextColumn()` (lines 908-910), where `TableNextColumn()` automatically wraps to the next row when the current row reaches its column limit. **Random access** uses `TableSetColumnIndex(int column_n)` (lines 910-911) to jump directly to specific columns, essential for irregular layouts where cells are skipped.

### Advanced Layout Controls

For fixed headers and sidebars, `TableSetupScrollFreeze(cols, rows)` (lines 922-923) locks the first *cols* columns and *rows* rows during scrolling. Visual highlighting is achieved through `TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1)` (lines 940-941), which supports cell, row, or column-level coloring.

## Complex Layout Patterns

### Nested Tables

Tables can embed sub-grids by calling `BeginTable` inside a cell. This pattern is useful for grouping related controls within a master-detail interface or creating hierarchical configuration panels without breaking the outer layout flow.

### Column Spanning

Irregular grids are constructed by using `TableSetColumnIndex()` to skip columns, then drawing widgets that visually span the remaining space. A full-width button, for example, can occupy an entire row by drawing at column 0 with a width of `-FLT_MIN` and never visiting subsequent columns in that row.

### Frozen Panes

Data-heavy applications use `TableSetupScrollFreeze` to lock header rows or identity columns in place while scrolling through large datasets. This creates Excel-like behavior where context remains visible during navigation.

### Variable Row Heights

While tables default to uniform row heights, passing a non-zero `min_row_height` to `TableNextRow()` or using `TableSetBgColor` with expanded vertical padding forces specific rows to consume additional vertical space.

### Custom Header Widgets

After calling `TableHeadersRow()`, developers can navigate back to header columns using `TableSetColumnIndex()` and render interactive widgets like combo boxes or filter inputs instead of static text labels.

## Practical Implementation Examples

### Sortable Data Grid with Scrolling

This example demonstrates a typical data grid with frozen headers, sortable columns, and alternating row backgrounds:

```cpp
if (ImGui::BeginTable("##DemoTable", 4,
        ImGuiTableFlags_Resizable |
        ImGuiTableFlags_Reorderable |
        ImGuiTableFlags_Sortable |
        ImGuiTableFlags_RowBg |
        ImGuiTableFlags_Borders |
        ImGuiTableFlags_ScrollY,
        ImVec2(0, ImGui::GetContentRegionAvail().y))) {
    
    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_DefaultSort);
    ImGui::TableSetupColumn("Health", ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableSetupColumn("Score", ImGuiTableColumnFlags_WidthFixed);
    ImGui::TableSetupColumn("Active", ImGuiTableColumnFlags_NoSort);
    ImGui::TableHeadersRow();

    for (int i = 0; i < 30; ++i) {
        ImGui::TableNextRow();
        ImGui::TableNextColumn(); ImGui::Text("Player %d", i);
        ImGui::TableNextColumn(); ImGui::ProgressBar(0.4f + i*0.02f);
        ImGui::TableNextColumn(); ImGui::Text("%d", 1000 - i*10);
        ImGui::TableNextColumn(); ImGui::Checkbox("##active", &playerActive[i]);
    }
    ImGui::EndTable();
}

```

### Nested Configuration Panels

This pattern embeds a sub-table within an outer table cell to create grouped settings:

```cpp
if (ImGui::BeginTable("##Outer", 2, ImGuiTableFlags_Borders)) {
    ImGui::TableNextRow();
    ImGui::TableNextColumn(); ImGui::Text("General Settings");

    ImGui::TableNextColumn();
    if (ImGui::BeginTable("##Inner", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
        ImGui::TableSetupColumn("Option");
        ImGui::TableSetupColumn("Value");
        ImGui::TableHeadersRow();

        ImGui::TableNextRow(); 
        ImGui::TableNextColumn(); ImGui::Text("VSync");   
        ImGui::TableNextColumn(); ImGui::Checkbox("##vsync", &vsync);
        
        ImGui::TableNextRow(); 
        ImGui::TableNextColumn(); ImGui::Text("FPS Cap"); 
        ImGui::TableNextColumn(); ImGui::SliderInt("##fps", &fpsCap, 30, 240);
        
        ImGui::EndTable();
    }
    ImGui::EndTable();
}

```

### Column Spanning and Interactive Headers

This example combines a full-width spanning button with a custom combo box header:

```cpp
if (ImGui::BeginTable("##SpanDemo", 3, ImGuiTableFlags_Borders)) {
    ImGui::TableSetupColumn("ID");
    ImGui::TableSetupColumn("Mode");
    ImGui::TableSetupColumn("Action");
    ImGui::TableHeadersRow();

    // Replace header of column 1 with a combo box
    ImGui::TableNextColumn(); // Skip ID header
    ImGui::TableNextColumn(); // Position in Mode header
    ImGui::SetNextItemWidth(-FLT_MIN);
    ImGui::Combo("##mode", &modeIdx, "Auto\0Manual\0");
    ImGui::TableNextColumn(); // Skip Action header

    // Full-width spanning row
    ImGui::TableNextRow();
    ImGui::TableSetColumnIndex(0);
    ImGui::Button("Full-Width Action", ImVec2(-FLT_MIN, 0));
    // Skip remaining columns to complete the row span

    // Standard rows
    for (int i = 0; i < 5; ++i) {
        ImGui::TableNextRow();
        ImGui::TableNextColumn(); ImGui::Text("Item %d", i);
        ImGui::TableNextColumn(); ImGui::Text("%s", (modeIdx == 0) ? "Auto" : "Manual");
        ImGui::TableNextColumn(); ImGui::Button("Run");
    }
    ImGui::EndTable();
}

```

## Internal Architecture

The table system maintains persistent state in the **`ImGuiTable`** structure (defined in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h), lines 3055-3119), which stores column definitions, sort specifications, and visibility flags. Each column is represented by **`ImGuiTableColumn`** (lines 2940-2990), containing flags, display order, and sizing weights.

Per-frame transient data lives in **`ImGuiTableTempData`**, allocated on the stack and reused across frames. This design makes iteration cheap, as the system only stores a compact description while the actual drawing is deferred to the **ImDrawList** during `EndTable()`. The implementation logic resides primarily in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp), with comprehensive usage examples available in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp).

## Summary

- **BeginTable** returns a boolean indicating visibility, allowing early-out for clipped tables
- **TableSetupColumn** defines column behavior including sorting, resizing, and initial width policies
- **TableSetColumnIndex** enables irregular layouts by allowing random access to column positions
- **TableSetupScrollFreeze** creates frozen panes for headers and sidebar columns
- Nested tables are supported by calling `BeginTable` within any table cell
- The API is state-driven and requires calling functions in the order: setup, headers, rows/cells, end

## Frequently Asked Questions

### When should I use the Table API instead of the old Columns API?

The Columns API is deprecated as of version 1.80. You should always use the Table API for new code, as it provides superior performance, better scrolling support, and features like sorting and freezing that the old system cannot replicate. The Table API also handles clipping and navigation automatically.

### How do I create cells that span multiple columns?

Use `TableSetColumnIndex(0)` to position at the first column, draw your widget with a full-width flag like `ImVec2(-FLT_MIN, 0)`, then simply do not call `TableNextColumn()` for the remaining columns in that row. The table cursor advances to the next row when you call `TableNextRow()`, effectively creating a spanning cell that occupies the entire row width.

### Are nested tables expensive in terms of performance?

No. Because table data uses transient stack allocation via `ImGuiTableTempData` and only persists the minimal `ImGuiTable` structure between frames, nested tables are inexpensive. The system does not recursively allocate large buffers; instead, it manages a compact description that the draw list renders efficiently during `EndTable()`.

### How do I implement column sorting in my table?

Enable sorting by including `ImGuiTableFlags_Sortable` in your `BeginTable` flags and `ImGuiTableColumnFlags_DefaultSort` in `TableSetupColumn` for sortable columns. After calling `TableHeadersRow()`, check `ImGui::TableGetSortSpecs()` to retrieve sort specifications, then reorder your data array accordingly before populating rows. The API handles the sort indicator UI automatically.