How to Use Dear ImGui’s Tables API (BeginTable/EndTable) for Complex Layouts

Dear ImGui’s tables API provides a modern, flexible grid system that replaces the legacy Columns API, allowing you to create sortable, scrollable, and nested layouts using BeginTable(), TableSetupColumn(), and TableNextRow() within the ocornut/imgui repository.

The Dear ImGui tables API (BeginTable/EndTable) offers a robust replacement for the deprecated Columns system, enabling complex widget arrangements with frozen headers, horizontal scrolling, and per-cell formatting. According to the ocornut/imgui source code, this system is implemented across imgui.h, imgui_tables.cpp, and imgui_internal.h, providing fine-grained control over layout sizing, sorting, and display. This guide covers the architectural workflow and practical implementation patterns for building sophisticated interface layouts.

Core Architecture of the Dear ImGui Tables API

The table system centers on a strict lifecycle that creates an internal child window capable of independent scrolling and clipping. The implementation in [imgui_tables.cpp](https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp) handles the layout engine, while the public interface is declared in [imgui.h](https://github.com/ocornut/imgui/blob/master/imgui.h#L882-L909).

The standard workflow follows this sequence:

  1. Initialize with BeginTable() – returns false if the table is clipped and should be skipped.
  2. Declare columns using TableSetupColumn() with sizing flags (e.g., ImGuiTableColumnFlags_WidthFixed) before the first row.
  3. Freeze rows/columns via TableSetupScrollFreeze() to keep headers visible during scrolling.
  4. Render headers with TableHeadersRow() or manually with TableNextRow() and TableHeader().
  5. Populate data by calling TableNextRow() followed by TableNextColumn() for each cell.
  6. Finalize with EndTable() (only if BeginTable() returned true).

Internal state structures such as ImGuiTable and per-column data are defined in [imgui_internal.h](https://github.com/ocornut/imgui/blob/master/imgui_internal.h#L3068-L3177).

Declaring Columns and Sizing Policies

Column definitions determine how space is distributed and how users can interact with the layout. Call TableSetupColumn() after BeginTable() but before any row operations:

  • ImGuiTableColumnFlags_WidthFixed: Allocates absolute pixel width (default 100.0f unless overridden).
  • ImGuiTableColumnFlags_WidthStretch: Distributes remaining space proportionally using weight values.
  • ImGuiTableColumnFlags_NoResize: Prevents manual resizing when combined with ImGuiTableFlags_Resizable.
  • ImGuiTableColumnFlags_DefaultSort: Marks the column as the default sort key when ImGuiTableFlags_Sortable is enabled.

The initial width or weight parameter in TableSetupColumn() interacts with the inner_width parameter of BeginTable() to determine the content region’s virtual size.

Implementing Scrolling and Frozen Headers

For tables that exceed their container bounds, horizontal scrolling is enabled by passing ImGuiTableFlags_ScrollX to BeginTable():

ImGui::BeginTable("Scrollable", 4, 
    ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX, 
    ImVec2(0.0f, 0.0f),  // outer_size
    0.0f);                // inner_width (0 = auto)

To freeze header rows or identifier columns, use TableSetupScrollFreeze(freeze_columns, freeze_rows) immediately after declaring columns. This keeps the specified rows and columns visible while the remainder of the table scrolls, implemented in the layout engine at [imgui_tables.cpp](https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp).

Row and Column Iteration Patterns

Populate tables by alternating between row and column advancement:

  • TableNextRow(): Advances to a new row, optionally specifying row flags and minimum height.
  • TableNextColumn(): Advances to the next column, automatically wrapping to the next row after the final column is reached.
  • TableSetColumnIndex(int column_n): Jumps to a specific column index, useful for random access or spanning cells.

Critical rule: The first call to TableNextColumn() must occur after TableNextRow() to establish the row context. Subsequent calls iterate through cells.

Advanced Complex Layout Techniques

Nested Tables

Tables can be nested because each BeginTable() creates an isolated child window. This allows complex hierarchical layouts where outer tables control global structure and inner tables manage detailed grids:

if (ImGui::BeginTable("Outer", 2, ImGuiTableFlags_Borders)) {
    ImGui::TableSetupColumn("Left");
    ImGui::TableSetupColumn("Right");
    ImGui::TableHeadersRow();

    ImGui::TableNextRow();
    ImGui::TableNextColumn();
    ImGui::Text("Static Content");

    ImGui::TableNextColumn();
    if (ImGui::BeginTable("Inner", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
        ImGui::TableSetupColumn("A");
        ImGui::TableSetupColumn("B");
        ImGui::TableSetupColumn("C");
        ImGui::TableHeadersRow();

        for (int i = 0; i < 4; ++i) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn(); ImGui::Text("R%dA", i);
            ImGui::TableNextColumn(); ImGui::Text("R%dB", i);
            ImGui::TableNextColumn(); ImGui::Text("R%dC", i);
        }
        ImGui::EndTable();
    }
    ImGui::EndTable();
}

Column Spanning and Background Colors

To span a cell across multiple columns, use TableSetColumnIndex() to position the cursor, then skip subsequent TableNextColumn() calls for the spanned columns. Customize cell appearance with TableSetBgColor():

if (ImGui::BeginTable("SpanTable", 3, ImGuiTableFlags_Borders)) {
    ImGui::TableSetupColumn("Name");
    ImGui::TableSetupColumn("Description");
    ImGui::TableSetupColumn("Value");
    ImGui::TableHeadersRow();

    ImGui::TableNextRow();
    ImGui::TableNextColumn(); 
    ImGui::Text("Item A");
    
    // Span description across remaining columns
    ImGui::TableSetColumnIndex(1);
    ImGui::Text("This spans the description");
    // Skip TableNextColumn() for column 2 to complete the span
    
    // Highlight entire row background
    ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, IM_COL32(30, 30, 160, 255));
    ImGui::EndTable();
}

Utility functions like TableSetColumnEnabled() dynamically hide columns, and TableGetSortSpecs() retrieves current sorting criteria when ImGuiTableFlags_Sortable is active.

Practical Implementation Examples

Fixed-Width Table with Headers

if (ImGui::BeginTable("SimpleTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
    ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed);
    ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
    ImGui::TableSetupColumn("Score", ImGuiTableColumnFlags_WidthFixed);
    ImGui::TableHeadersRow();

    for (int i = 0; i < 5; ++i) {
        ImGui::TableNextRow();
        ImGui::TableNextColumn(); ImGui::Text("%d", i);
        ImGui::TableNextColumn(); ImGui::Text("Player %d", i);
        ImGui::TableNextColumn(); ImGui::Text("%d", rand() % 100);
    }
    ImGui::EndTable();
}

Stretch Columns with Horizontal Scrolling

ImGui::BeginTable("StretchTable", 4,
    ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX | ImGuiTableFlags_SizingStretchSame,
    ImVec2(0.0f, 0.0f), 0.0f);
{
    ImGui::TableSetupColumn("A");
    ImGui::TableSetupColumn("B");
    ImGui::TableSetupColumn("C");
    ImGui::TableSetupColumn("D");
    ImGui::TableHeadersRow();

    for (int row = 0; row < 10; ++row) {
        ImGui::TableNextRow();
        for (int col = 0; col < 4; ++col) {
            ImGui::TableNextColumn();
            ImGui::Text("R%dC%d", row, col);
        }
    }
}
ImGui::EndTable();

Summary

Frequently Asked Questions

How do I enable horizontal scrolling in a Dear ImGui table?

Pass ImGuiTableFlags_ScrollX to BeginTable() and specify an inner_width parameter larger than the outer size or zero for automatic sizing. This creates a child window that clips and scrolls horizontally according to the implementation in [imgui_tables.cpp](https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp).

Can I nest tables inside other tables?

Yes, calling BeginTable() inside a cell creates an independent child window with its own scroll and layout context. This allows complex hierarchical designs where outer tables manage structural layout and inner tables present detailed data grids.

What is the difference between WidthFixed and WidthStretch columns?

WidthFixed allocates a specific pixel width defined in TableSetupColumn(), while WidthStretch distributes remaining host window space proportionally based on weight values. Mixing both types allows fixed-width identifiers alongside fluid content columns.

How do I freeze header rows so they remain visible while scrolling?

Call TableSetupScrollFreeze(columns, rows) immediately after declaring columns, typically with TableSetupScrollFreeze(0, 1) to freeze the first row. This modifies the clipping region in the table layout engine to keep frozen sections visible during vertical or horizontal scroll operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →