How to Implement Custom InputText Behavior with ImGuiInputTextCallback in Dear ImGui

To implement custom InputText behavior in Dear ImGui, pass one or more ImGuiInputTextFlags_Callback* flags to InputText() or InputTextMultiline(), then handle events like resize, character filtering, or completion inside a callback function that manipulates the ImGuiInputTextCallbackData structure.

The ocornut/imgui library provides a powerful callback system that lets you intercept and modify text input at multiple stages of the editing pipeline. By understanding the ImGuiInputTextCallbackData structure and the specific flags available, you can implement dynamic resizing, input validation, auto-completion, and history navigation.

Understanding the ImGuiInputTextCallback System

Dear ImGui's InputText() family functions are thin wrappers around the internal InputTextEx() implementation located in imgui_widgets.cpp. When you pass callback flags, the widget constructs an ImGuiInputTextCallbackData object and invokes your user-supplied function whenever the corresponding event occurs.

The following flags control when your callback executes:

  • ImGuiInputTextFlags_CallbackResize – Triggered when the buffer needs to grow beyond the initially provided buf_size. Use this to reallocate backing storage and update data->Buf and data->BufSize.
  • ImGuiInputTextFlags_CallbackEdit – Called after any edit operation (insertion, deletion). Inspect or modify data->Buf, CursorPos, and SelectionStart/End.
  • ImGuiInputTextFlags_CallbackAlways – Executed every frame while the widget has focus. Ideal for live validation and visual feedback.
  • ImGuiInputTextFlags_CallbackCharFilter – Invoked for each typed character before insertion. Replace data->EventChar or discard it by returning 1.
  • ImGuiInputTextFlags_CallbackCompletion – Fired when the user presses TAB. Implement suggestion logic or insert completion characters here.
  • ImGuiInputTextFlags_CallbackHistory – Triggered on ↑/↓ key presses. Use this to navigate a custom command history.

The callback data structure is defined in imgui.h (around line 2670) and provides helper methods like InsertChars() and DeleteChars() that automatically respect the resize callback.

Implementing the Core Callback Handler

To create a robust implementation, follow this workflow:

  1. Create a user-data struct to hold your dynamic buffer (e.g., std::string) and any additional state or chained callbacks.
  2. Set the appropriate flags when calling InputText() or InputTextMultiline().
  3. Switch on data->EventFlag inside your callback to handle specific events:
    • For CallbackResize, reallocate your buffer, update data->Buf with the new pointer, and set data->BufSize.
    • For CallbackCharFilter, inspect data->EventChar and return 1 to discard unwanted characters.
    • For edit or always callbacks, modify data->Buf directly and set data->BufDirty = true to signal changes.
  4. Return 0 to accept the event (except for character filtering where returning 1 discards the character).

The widget maintains its internal state in ImGuiInputTextState (a member of ImGuiContext), and the callback is invoked from InputTextEx() after the state processes the event. Prefer using InsertChars() and DeleteChars() over manual buffer manipulation, as these methods handle reallocation boundaries safely.

Complete Example: Dynamic std::string with Character Filtering

Below is a production-ready pattern that combines dynamic resizing with input validation. This example uses a std::string as backing storage and rejects numeric digits.

// User-data structure holding the string and optional chained callback
struct InputTextUserData {
    std::string* Str;
    ImGuiInputTextCallback UserCallback = nullptr;
    void* UserCallbackData = nullptr;
};

// The callback implementation
int CustomInputCallback(ImGuiInputTextCallbackData* data)
{
    InputTextUserData* user = (InputTextUserData*)data->UserData;

    // Handle buffer resize for dynamic std::string
    if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
    {
        std::string& s = *user->Str;
        IM_ASSERT(data->Buf == s.c_str());  // Sanity check
        s.resize(data->BufTextLen);
        data->Buf = (char*)s.c_str();
        data->BufSize = s.capacity() + 1;
        return 0;
    }

    // Filter out digits (0-9)
    if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter)
    {
        if (data->EventChar >= '0' && data->EventChar <= '9')
            return 1;  // Discard digit
    }

    // Support callback chaining
    if (user->UserCallback)
    {
        data->UserData = user->UserCallbackData;
        return user->UserCallback(data);
    }

    return 0;
}

// Usage in your GUI code
void ShowCustomInput()
{
    static std::string text;
    InputTextUserData cb_data = { &text, nullptr, nullptr };
    
    ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize |
                                ImGuiInputTextFlags_CallbackCharFilter;
    
    ImGui::InputText("Name (letters only)", &text, flags, 
                     CustomInputCallback, &cb_data);
}

Key implementation details:

  • The resize callback ensures the std::string grows automatically as the user types.
  • The character filter intercepts digits before they reach the buffer.
  • The callback uses data->BufTextLen to determine the required size, not strlen().

Common Callback Patterns

Use these patterns to solve specific interaction requirements:

Auto-Completion (TAB) Enable ImGuiInputTextFlags_CallbackCompletion and check for data->EventKey == ImGuiKey_Tab. Use data->InsertChars() to append suggestions at data->CursorPos.

History Navigation (↑/↓) With ImGuiInputTextFlags_CallbackHistory, detect data->EventKey for Up/Down arrows. Maintain a std::vector<std::string> history and replace data->Buf content using DeleteChars() followed by InsertChars().

Live Validation Combine ImGuiInputTextFlags_CallbackEdit or CallbackAlways with regex or custom logic. If validation fails, modify data->Buf to correct the input and set data->BufDirty = true.

Simple Resize Wrapper For cases requiring only dynamic resizing without other logic, reference the implementation in misc/cpp/imgui_stdlib.cpp, which demonstrates the minimal resize callback pattern for std::string integration.

Summary

  • InputText() and InputTextMultiline() forward to InputTextEx() in imgui_widgets.cpp, which manages the callback dispatch loop.
  • Use ImGuiInputTextFlags_CallbackResize to support dynamic buffers like std::string by updating data->Buf and data->BufSize during reallocation.
  • Implement ImGuiInputTextFlags_CallbackCharFilter to sanitize input character-by-character before insertion.
  • Prefer InsertChars() and DeleteChars() over raw buffer manipulation to ensure automatic resize handling.
  • Chain callbacks by storing the original callback pointer in your user-data struct and invoking it after your custom logic.

Frequently Asked Questions

What is the difference between CallbackEdit and CallbackAlways?

ImGuiInputTextFlags_CallbackEdit fires only when the buffer content actually changes (insertions, deletions, or replacements), making it efficient for validation logic that must run after modifications. ImGuiInputTextFlags_CallbackAlways executes every frame while the widget retains focus, which is useful for continuous visual feedback or handling special keys like Enter for auto-indentation, even when no text changes occur.

How do I properly resize a std::string inside the callback?

When handling ImGuiInputTextFlags_CallbackResize, resize your std::string to data->BufTextLen, then update data->Buf with (char*)s.c_str() and data->BufSize with the new capacity plus one. Always verify that data->Buf matches your string's internal pointer before resizing to ensure memory consistency.

Can I chain multiple callbacks together?

Yes. Store the additional callback function pointer and its user data inside your own user-data structure. After processing your custom logic, forward the call by setting data->UserData to the stored user data and returning the result of the chained callback function.

Why is my buffer pointer invalid after resizing?

If you trigger a buffer modification (like InsertChars()) without the ImGuiInputTextFlags_CallbackResize flag set, or if you manually write to data->Buf without updating the pointer after a resize, the internal state may reference freed memory. Always ensure the resize flag is set when using dynamic buffers, and update data->Buf immediately after any reallocation.

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 →