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:
- Initialize with
BeginTable()– returnsfalseif the table is clipped and should be skipped. - Declare columns using
TableSetupColumn()with sizing flags (e.g.,ImGuiTableColumnFlags_WidthFixed) before the first row. - Freeze rows/columns via
TableSetupScrollFreeze()to keep headers visible during scrolling. - Render headers with
TableHeadersRow()or manually withTableNextRow()andTableHeader(). - Populate data by calling
TableNextRow()followed byTableNextColumn()for each cell. - Finalize with
EndTable()(only ifBeginTable()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 withImGuiTableFlags_Resizable.ImGuiTableColumnFlags_DefaultSort: Marks the column as the default sort key whenImGuiTableFlags_Sortableis 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
- BeginTable() creates a scoped child window that must be paired with EndTable(), returning
falsewhen clipped. - Declare column properties using TableSetupColumn() before the first row, utilizing
ImGuiTableColumnFlags_WidthFixedorImGuiTableColumnFlags_WidthStretchfor precise sizing control. - Iterate through the grid using TableNextRow() followed by TableNextColumn(), which automatically wraps after the last column.
- Enable TableSetupScrollFreeze() to keep headers visible during scrolling, and pass ImGuiTableFlags_ScrollX for horizontal overflow.
- Implement complex hierarchies with nested tables, column spanning via TableSetColumnIndex(), and visual customization with TableSetBgColor().
- Reference the API declarations in [
imgui.h](https://github.com/ocornut/imgui/blob/master/imgui.h#L882-L909), the layout engine in [imgui_tables.cpp](https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp), and internal structures in [imgui_internal.h](https://github.com/ocornut/imgui/blob/master/imgui_internal.h#L3068-L3177).
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →