# How to Create Multi-Column Layouts with Sorting and Resizing Using ImGui Tables

> Master ImGui Tables to build layouts with sorting and resizing. Learn to declare columns, set flags, and manage sort specs for dynamic data presentation. Enhance your UI now.

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

---

**To create multi-column layouts with sorting and resizing in ImGui, call `ImGui::BeginTable()` with `ImGuiTableFlags_Resizable` and `ImGuiTableFlags_Sortable`, declare columns using `ImGui::TableSetupColumn()`, emit headers with `ImGui::TableHeadersRow()`, and retrieve sort specifications via `ImGui::TableGetSortSpecs()` to reorder your underlying data.**

The Dear ImGui library (`ocornut/imgui`) implements a self-contained Tables API in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) that supports complex **multi-column layouts with sorting and resizing** through a persistent `ImGuiTable` instance stored in the global context. The public interface lives in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), providing everything needed to create interactive data grids without external dependencies.

## Understanding the Tables API Architecture

ImGui handles tables through a single module architecture. The heavy lifting occurs in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp), while the public-facing functions are declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h). When you create a table, the system allocates (or reuses) an `ImGuiTable` instance stored in `GImGui`, which persists across frames to maintain state such as column widths, display order, and sort specifications without requiring manual storage in your application code.

## Essential Flags for Resizable and Sortable Tables

The table behavior is controlled by flags passed to `BeginTable()`:

- **`ImGuiTableFlags_Resizable`** – Enables dragging column borders to resize. Internally, resize requests are queued and applied via `TableApplyQueuedRequests()` around lines 78-90 in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp), which calls `TableSetColumnWidth()` to update `WidthAuto` and recalculate stretch weights.
- **`ImGuiTableFlags_Sortable`** – Allows clicking headers to toggle sort order. Click handling occurs in `TableSortSpecsClickColumn()` (around lines 58-60), which updates the `ImGuiTableSortSpec` array and marks the table for resorting.
- **`ImGuiTableFlags_Reorderable`** – Permits dragging headers to change column order. The request is stored in `TableQueueSetColumnDisplayOrder()` and applied later by `TableSetColumnDisplayOrder()` (lines 777-785).
- **`ImGuiTableFlags_ScrollX / ScrollY`** – Creates an internal scrolling child window within `BeginTableEx()` (lines 40-46), essential when total column width exceeds the available outer size.

## Step-by-Step Table Creation Flow

### Step 1: Initialize with BeginTable

Call `ImGui::BeginTable(const char* id, int columns, ImGuiTableFlags flags, ImVec2 outer_size = ImVec2(0,0), float inner_width = 0)` to allocate the table instance and determine sizing policies through `TableFixFlags()`. An outer size of `(0,0)` allows the table to fill remaining window space.

### Step 2: Declare Columns with TableSetupColumn

Define each column using `ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0, ImGuiID user_data = 0)`. This must be called **before the first row** (enforced by an assertion in the implementation) to store per-column metadata including width policy (`WidthFixed` or `WidthStretch`) and optional user IDs for sorting.

### Step 3: Emit Headers with TableHeadersRow

Invoke `ImGui::TableHeadersRow()` to iterate over declared columns and render header cells. This function automatically registers click and resize interactions. When `ImGuiTableFlags_Sortable` is active, left-clicks trigger `TableSortSpecsClickColumn()` to update sort direction.

### Step 4: Fill Rows and Cells

Use `ImGui::TableNextRow()` to create a new row and `ImGui::TableNextColumn()` to advance to the next cell. Row height is computed from the tallest visible cell, ensuring consistent alignment even with hidden columns.

### Step 5: Finalize with EndTable

Call `ImGui::EndTable()` to complete drawing, merge draw channels, and persist settings to the `.ini` file if enabled.

## Implementing Sort Logic

After `TableHeadersRow()`, retrieve the current sort specifications:

```cpp
const ImGuiTableSortSpecs* sorts = ImGui::TableGetSortSpecs();
if (sorts && sorts->SpecsCount > 0)
{
    for (int n = 0; n < sorts->SpecsCount; n++)
    {
        const ImGuiTableSortSpec* spec = &sorts->Specs[n];
        // spec->ColumnIndex, spec->SortDirection, spec->ColumnUserID
    }
}

```

The `ImGuiTableSortSpec` struct contains `ColumnIndex`, `SortOrder` (for multi-column sorts), `SortDirection` (ascending/descending), and `ColumnUserID` (if specified in `TableSetupColumn()`). Your application reads this data and reorders its data source accordingly.

## Managing Resizing and Reordering Internally

When a user drags a column border, ImGui records `ResizedColumn` and `ResizedColumnNextWidth`. On the subsequent frame, `TableApplyQueuedRequests()` (called from `BeginTableEx`) invokes `TableSetColumnWidth()` to apply the new width. For reordering, drag operations queue via `TableQueueSetColumnDisplayOrder()` and are processed by `TableSetColumnDisplayOrder()` to update the display indices persistently.

## Complete Implementation Examples

### Example 1: Basic Resizable, Sortable, and Reorderable Table

```cpp
if (ImGui::BeginTable("my_table", 3,
                     ImGuiTableFlags_Resizable |
                     ImGuiTableFlags_Sortable |
                     ImGuiTableFlags_Reorderable |
                     ImGuiTableFlags_Borders |
                     ImGuiTableFlags_RowBg))
{
    // Define columns: Fixed width for ID, stretch for others
    ImGui::TableSetupColumn("ID",    ImGuiTableColumnFlags_WidthFixed,   80.0f);
    ImGui::TableSetupColumn("Name",  ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableSetupColumn("Score", ImGuiTableColumnFlags_WidthStretch);
    
    ImGui::TableHeadersRow();

    struct Item { int id; const char* name; int score; };
    static Item items[] = { {1,"Alice", 95}, {2,"Bob", 78}, {3,"Carol", 88} };

    // Handle sorting
    const ImGuiTableSortSpecs* sorts = ImGui::TableGetSortSpecs();
    if (sorts && sorts->SpecsCount > 0)
    {
        const ImGuiTableSortSpec* spec = sorts->Specs;
        std::stable_sort(std::begin(items), std::end(items),
            [spec](const Item& a, const Item& b)
            {
                if (spec->ColumnIndex == 0)
                    return (spec->SortDirection == ImGuiSortDirection_Ascending) ? 
                           a.id < b.id : a.id > b.id;
                else if (spec->ColumnIndex == 2)
                    return (spec->SortDirection == ImGuiSortDirection_Ascending) ? 
                           a.score < b.score : a.score > b.score;
                return false;
            });
    }

    // Populate rows
    for (const Item& it : items)
    {
        ImGui::TableNextRow();
        ImGui::TableNextColumn(); ImGui::Text("%d", it.id);
        ImGui::TableNextColumn(); ImGui::TextUnformatted(it.name);
        ImGui::TableNextColumn(); ImGui::Text("%d", it.score);
    }
    ImGui::EndTable();
}

```

### Example 2: Scrollable Table with Frozen Columns

```cpp
if (ImGui::BeginTable("scrollable_table", 5,
                     ImGuiTableFlags_Resizable |
                     ImGuiTableFlags_Sortable |
                     ImGuiTableFlags_ScrollX |
                     ImGuiTableFlags_ScrollY |
                     ImGuiTableFlags_FreezeFirstColumn |
                     ImGuiTableFlags_Borders))
{
    // Freeze first 2 columns horizontally
    ImGui::TableSetupScrollFreeze(2, 0);

    ImGui::TableSetupColumn("Idx",     ImGuiTableColumnFlags_WidthFixed,   50);
    ImGui::TableSetupColumn("Name",    ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableSetupColumn("Age",     ImGuiTableColumnFlags_WidthFixed,   40);
    ImGui::TableSetupColumn("Country", ImGuiTableColumnFlags_WidthStretch);
    ImGui::TableSetupColumn("Score",   ImGuiTableColumnFlags_WidthFixed,   60);

    ImGui::TableHeadersRow();

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

```

### Example 3: Sorting with Custom User IDs

```cpp
if (ImGui::BeginTable("custom_id_table", 2,
                     ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable))
{
    // Assign stable IDs to identify columns by logical name rather than index
    ImGui::TableSetupColumn("File",      ImGuiTableColumnFlags_None, 0.0f, (ImGuiID)1);
    ImGui::TableSetupColumn("Size (KB)", ImGuiTableColumnFlags_None, 0.0f, (ImGuiID)2);
    ImGui::TableHeadersRow();

    const ImGuiTableSortSpecs* sort = ImGui::TableGetSortSpecs();
    if (sort && sort->SpecsCount > 0)
    {
        const ImGuiTableSortSpec* spec = sort->Specs;
        // Use spec->ColumnUserID (1 or 2) to determine sort logic
        // rather than relying on spec->ColumnIndex
    }

    // ... fill rows ...
    ImGui::EndTable();
}

```

## Key Source Files for Reference

| File | Purpose | Location |
|------|---------|----------|
| [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) | Public API declarations for `BeginTable`, `TableSetupColumn`, `TableHeadersRow`, and `TableGetSortSpecs` | [imgui.h](https://github.com/ocornut/imgui/blob/master/imgui.h) |
| [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) | Core implementation including `TableApplyQueuedRequests`, `TableSortSpecsClickColumn`, and `TableSetColumnDisplayOrder` | [imgui_tables.cpp](https://github.com/ocornut/imgui/blob/master/imgui_tables.cpp) |
| [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) | Real-world usage examples demonstrating column setup, resizing policies, and sorting implementations | [imgui_demo.cpp](https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp) |

## Summary

- Use `ImGuiTableFlags_Resizable` and `ImGuiTableFlags_Sortable` in `BeginTable()` to enable interactive column features.
- Call `TableSetupColumn()` before the first row to define width policies (`WidthFixed` vs `WidthStretch`) and optional user IDs.
- Invoke `TableHeadersRow()` to generate clickable headers that automatically handle sort direction toggles and resize interactions.
- Access `ImGui::TableGetSortSpecs()` after drawing headers to retrieve an array of `ImGuiTableSortSpec` containing `ColumnIndex`, `SortDirection`, and `ColumnUserID`.
- Resize and reorder requests are queued internally in `TableApplyQueuedRequests()` and applied on the next frame via functions like `TableSetColumnWidth()` and `TableSetColumnDisplayOrder()`.
- The `ImGuiTable` instance persists across frames in the global context, automatically preserving column widths, order, and sort state.

## Frequently Asked Questions

### How do I disable resizing for specific columns only?

Pass `ImGuiTableColumnFlags_NoResize` to `TableSetupColumn()` for that specific column while keeping `ImGuiTableFlags_Resizable` on the table. This allows most columns to be resized while freezing the width of critical columns.

### Can I sort by multiple columns simultaneously?

Yes. When multiple sort criteria are active, `TableGetSortSpecs()` returns an array where `SpecsCount` indicates how many columns are involved in the sort. Iterate through all `ImGuiTableSortSpec` entries, each providing `ColumnIndex`, `SortOrder` (priority), and `SortDirection` to implement multi-level sorting in your comparison function.

### How do I persist column widths between application restarts?

ImGui automatically saves table settings including column widths to the `.ini` file when `ImGuiTableFlags_SaveSettings` is enabled (default in most configurations), provided `io.IniFilename` is set. The settings are loaded automatically on startup and saved when the context shuts down or when `SaveIniSettingsToDisk()` is called.

### Why must TableSetupColumn be called before the first row?

The implementation in [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp) enforces this via an assertion because column metadata must be fully initialized before `BeginTableEx()` calculates the initial layout and before `TableHeadersRow()` iterates over the column array to render headers and register interaction hit boxes.