How to Load and Use Multiple Fonts with ImFontAtlas in Dear ImGui

To load and use multiple fonts with ImFontAtlas, add each font to io.Fonts before the first frame using AddFontFromFileTTF(), store the returned ImFont* pointers, and switch between them at runtime with ImGui::PushFont() and ImGui::PopFont().

Dear ImGui manages all fonts through a single ImFontAtlas object accessed via ImGui::GetIO().Fonts. According to the ocornut/imgui source code, the atlas builds a unified texture containing every glyph from every font you add, allowing you to load and use multiple fonts with ImFontAtlas efficiently in a single draw call.

Understanding the ImFontAtlas Architecture

The ImFontAtlas class, defined in imgui.h (lines 761-777), acts as a container and rasterizer for all your fonts. When you call AddFontFromFileTTF() or similar methods, the atlas populates an internal texture atlas that the renderer uploads to the GPU. This design means you can load dozens of fonts and variants without changing your rendering pipeline.

In imgui.cpp (lines 45-53), the default context initialization ensures io.Fonts is never null, automatically calling AddFontDefault() if you haven't added any custom fonts before the first frame.

Loading Multiple Fonts Before the First Frame

You must create all fonts after ImGui::CreateContext() but before the first call to ImGui::NewFrame(). This timing ensures the atlas builds correctly and your renderer uploads the texture once.

Basic Font Loading with AddFontFromFileTTF

The primary method for loading TTF files is ImFontAtlas::AddFontFromFileTTF(), implemented in imgui_draw.cpp (lines 3239-3245). Each call returns an ImFont* pointer that you should store if you intend to switch to that font later.

ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();

// Load different fonts with specific sizes
ImFont* font_mono = io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 16.0f);
ImFont* font_large = io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 24.0f);

Loading the Default Font

If you need a guaranteed fallback, call AddFontDefault() first. This embedded font loads instantly and serves as a backup when file-based fonts fail to load.

ImFont* font_default = io.Fonts->AddFontDefault();  // Proggy Clean, 13px

Configuring Fonts with ImFontConfig

The ImFontConfig struct gives you fine-grained control over rasterization, merging, and glyph selection. Pass a pointer to this struct as the third parameter to AddFontFromFileTTF().

Merging Icon Fonts into the Main Font

To render icons alongside text without PushFont calls, use MergeMode. Set ImFontConfig::MergeMode = true on secondary fonts to combine their glyphs into the previously added font's texture space.

ImFont* main_font = io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f);

ImFontConfig icons_cfg;
icons_cfg.MergeMode = true;                    // Merge into previous font
icons_cfg.GlyphMinAdvanceX = 13.0f;            // Align icons width

static const ImWchar icon_ranges[] = { 0xF000, 0xF3FF, 0 };
ImFont* font_icon = io.Fonts->AddFontFromFileTTF(
    "fonts/fontawesome-webfont.ttf", 13.0f, &icons_cfg, icon_ranges);

Now main_font contains both text and icon glyphs, letting you write:

ImGui::Text("Camera %s", ICON_FA_CAMERA);  // No font switching needed

Setting Custom Glyph Ranges

For Chinese, Japanese, or Korean text, specify Unicode ranges to avoid loading the entire font:

static const ImWchar ranges[] = { 0x0020, 0x00FF, 0x3000, 0x30FF, 0 };
ImFontConfig cfg;
ImFont* font_jp = io.Fonts->AddFontFromFileTTF("fonts/NotoSansJP.ttf", 18.0f, &cfg, ranges);

Building the Font Atlas

ImGui automatically builds the atlas on the first call to NewFrame() if any fonts were added or configurations changed. However, you can force immediate building with Build():

io.Fonts->Build();  // Optional: forces rasterization before first frame

If you add fonts after the first frame (hot-reloading), you must call Build() and refresh your renderer's device objects. Most backends provide ImGui_ImplXXX_CreateDeviceObjects() for this purpose.

Switching Between Fonts at Runtime

To render text in a specific font, use the stack-based PushFont/PopFont API.

The PushFont/PopFont Pattern

Surround your UI code with these calls to temporarily change the font:

ImGui::PushFont(font_mono);
ImGui::Text("Code sample: int x = 42;");
ImGui::PopFont();  // Restore previous font

You can nest these calls safely. The example in imgui_demo.cpp (lines 3768-3792) demonstrates loading several fonts and switching between them for headers and body text:

ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontDefault();
ImFont* large = io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 24.0f);
ImFont* small = io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 12.0f);

// Usage
ImGui::PushFont(large);
ImGui::Text("Large heading");
ImGui::PopFont();

ImGui::PushFont(small);
ImGui::Text("Small footnote");
ImGui::PopFont();

Using the Default Font

If you never call PushFont, ImGui uses the first font added (the default). If you only loaded AddFontDefault(), that becomes your primary font.

Hot-Reloading Fonts After Initialization

To update fonts at runtime (e.g., changing sizes based on DPI):

  1. Clear existing fonts: io.Fonts->Clear()
  2. Re-add your fonts with new configurations
  3. Call io.Fonts->Build()
  4. Refresh GPU textures via your backend's recreation function
// Clear and reload
io.Fonts->Clear();
ImFont* new_font = io.Fonts->AddFontFromFileTTF("fonts/Arial.ttf", new_size);
io.Fonts->Build();

// Backend-specific texture refresh
ImGui_ImplOpenGL3_CreateDeviceObjects();

Summary

  • Load early: Add fonts after CreateContext() but before the first NewFrame() to avoid rebuilding.
  • Store pointers: Keep the ImFont* returned by AddFontFromFileTTF() for later use with PushFont.
  • Merge icons: Use ImFontConfig::MergeMode = true to combine icon fonts with text fonts, eliminating the need for PushFont swaps.
  • Switch at runtime: Use PushFont(font) and PopFont() to change fonts for specific widgets.
  • Rebuild when changing: Call io.Fonts->Build() and refresh backend device objects if you modify fonts after the first frame.

Frequently Asked Questions

Can I load fonts after the first frame has already rendered?

Yes, but you must manually trigger a rebuild. Call io.Fonts->Clear(), re-add your fonts, then io.Fonts->Build(). Finally, call your renderer backend's device object creation function (such as ImGui_ImplOpenGL3_CreateDeviceObjects()) to upload the new texture atlas to the GPU.

How do I use an icon font like FontAwesome with a regular text font?

Add the text font first, then configure an ImFontConfig with MergeMode = true and pass the specific icon Unicode ranges to AddFontFromFileTTF(). This merges the icon glyphs into the same font atlas slot, allowing you to render text and icons together without PushFont calls.

What happens if I don't call PushFont before drawing text?

If you never push a custom font, Dear ImGui uses the default font—the first one added to the atlas, or the built-in Proggy Clean font if you called AddFontDefault(). The default font is automatically selected when no other font is on the stack.

Why are my custom fonts not appearing or showing as blank boxes?

This usually happens when the font file path is incorrect or the atlas failed to build. Verify the file exists at the specified path relative to your working directory. Also ensure you are loading fonts before the first NewFrame() call, or if loading later, that you called Build() and refreshed your renderer's textures. Blank boxes typically indicate missing glyphs in the atlas.

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 →