Right direction. I’d separate “less WinAPI boilerplate” from “real GUI toolkit”. In your project, the app remains a Windows .exe distributed through Steam, and Linux users run the same binary through Proton. So libraries should be judged by: Windows build quality, ease of cross-compilation from Linux, Proton behavior, DLL/dependency burden, and whether they still let you ship a boring Win64 executable.
My recommendation
For this specific project, I would probably evolve in stages:
Stage 1: keep Win32, but wrap it yourself + use zigwin32
Stage 2: introduce a rendering/UI layer, likely Dear ImGui or Nuklear
Stage 3: only move to SDL/raylib/libui/Qt-like territory if the app becomes much larger
For a drawing app, I would not jump straight to a traditional widget toolkit unless you know you want native controls, complex dialogs, tree views, document tabs, accessibility, etc. A paint-like app is mostly: window, input, canvas, menus, dialogs, drawing surface. That maps very well to a small custom app framework plus a lightweight UI layer.
Option 1: zigwin32 + your own thin app framework
This is the least disruptive next step.
zigwin32 is an autogenerated set of Zig bindings for the Win32 API, generated by zigwin32gen; the repository describes itself as “a complete autogenerated set of Zig bindings for the Win32 API.” [GitHub]
This does not hide the WinAPI model. You still have windows, handles, messages, WndProc, GDI objects, etc. But it can improve correctness and reduce hand-written extern declarations.
You then write your own thin layer:
const App = struct {
hwnd: HWND,
canvas: Canvas,
drawing: bool,
pub fn onPaint(self: *App, dc: HDC) void {}
pub fn onMouseDown(self: *App, x: i32, y: i32) void {}
pub fn onMouseMove(self: *App, x: i32, y: i32) void {}
pub fn onCommand(self: *App, id: u16) void {}
};
And hide the Windows message plumbing behind something like:
pub fn run(comptime AppType: type, app: *AppType) !void {
registerWindowClass(...);
createMainWindow(...);
messageLoop();
}
The result is still “Win32-native”, but your application code stops looking like 1998 C.
This is my favorite near-term path because it keeps your current Proton behavior almost unchanged. You are already testing a Windows .exe; replacing raw declarations with generated bindings and wrapping the event loop should not introduce new runtime risk.
Pros
Very small dependency surface.
No extra DLLs.
Excellent Proton compatibility expectations.
You understand the underlying model.
You can design Zig-native APIs instead of fighting a C GUI toolkit.
Cons
You still own the framework.
You still need to understand Win32 concepts.
Native widgets beyond menus/dialogs remain manual.
Best use:
Main window, menu, dialogs, input, canvas, timers, clipboard, file dialogs,
DPI handling, app state, basic GDI/Direct2D wrappers.
Option 2: Dear ImGui
Dear ImGui is probably the strongest option if you are okay with a non-native immediate-mode UI. The official repository says it has maintained backends for platforms including Win32 and renderers including DirectX 9/10/11/12, OpenGL, Vulkan, SDL renderer, SDL GPU, WebGPU, etc. [GitHub] Its Win32 backend is explicitly for standard Windows API applications, both 32-bit and 64-bit. [GitHub]
For your app, you could keep:
Win32 window
Win32 message loop
Dear ImGui for toolbars/panels/dialog-like UI
A canvas region for drawing
You still need a renderer backend. For Proton-friendliness and “not a 3D app”, I’d still consider a simple Direct3D 11 backend because Dear ImGui’s mature examples are built around renderer backends, and Proton’s DXVK/D3D path is very well-trodden. Alternatively, OpenGL works but on Windows-through-Proton it still passes through Wine’s OpenGL path. For a Paint clone, either is likely fine, but D3D11 is the more “normal Windows app/game” route under Proton.
Zig integration choices are less standardized than C++; you can either:
compile Dear ImGui C++ code as part of the Zig build,
use cimgui-style C bindings,
or use a Zig gamedev binding package if it fits your Zig version.
The web results also point to the zig-gamedev ecosystem as a place where Zig ImGui bindings are commonly discussed, but I would treat those bindings as version-sensitive rather than as a forever-stable platform API. [Reddit]
Pros
Very productive for tools.
Good for panes, sliders, buttons, color pickers, debug UI.
Works well for canvas-heavy apps.
Avoids native-control boilerplate.
Widely used and maintained.
Cons
Not native-looking.
Text editing, accessibility, IME, platform polish need evaluation.
Requires renderer integration.
C++ dependency unless using generated C bindings.
Best use:
Tool palettes, brush settings, layers panel, status bar, debug windows,
dockable tool UI, prototype-heavy app growth.
For a Paint clone that may grow into a creative/tool app, this is probably the most productive “real” UI layer.
Option 3: Nuklear
Nuklear is a smaller immediate-mode GUI library written in ANSI C; current descriptions emphasize that it is lightweight, portable, self-contained, and single-header-style. [SourceForge] There are Zig bindings such as zig-nuklear, but that project describes itself as WIP bindings, with examples using GLFW/OpenGL. [GitHub]
Nuklear is attractive if you want something simpler than Dear ImGui, easier to vendor, and less C++-shaped.
Pros
C, not C++.
Small.
Good fit for Zig @cImport or thin bindings.
Immediate-mode model fits drawing tools.
Cons
Less polished ecosystem than Dear ImGui.
You may need to own more backend/input/rendering code.
Zig bindings may be less mature.
Native menus/dialogs still need Win32 or your own wrappers.
Best use:
Small tool UI, custom panels, minimal dependencies, experiments.
I would consider Nuklear if Dear ImGui feels too large or too C++-heavy.
Option 4: SDL3 + custom UI / ImGui / Nuklear
SDL is not a GUI toolkit; it is a windowing/input/audio/etc. layer. In a 2026 SDL discussion, the answer is blunt: “SDL has no GUI functionality,” so you pair it with your own GUI or something like ImGui. [Simple Directmedia Layer]
That said, SDL3 is very relevant to Steam/Proton-style apps. There is a Zig-build-system port of SDL3, allyourcodebase/SDL3, whose README says it supports cross-compilation and custom platform configuration. [GitHub]
This would change your app from:
to:
SDL3 window/events
SDL renderer/GPU or software surface
Dear ImGui/Nuklear/custom UI
But remember: you are building a Windows executable. You would link/bundle SDL’s Windows library/DLL, then run that same Windows build under Proton.
Pros
Hides window/message/input plumbing.
Good cross-platform-ish API even though you ship Windows only.
Works naturally with game/tool UI stacks.
Steam-ish ecosystem comfort.
Cons
Not a native Windows widget toolkit.
You lose normal Win32 menu/dialog model unless you bridge back.
Requires bundling SDL DLL unless statically linked.
A bit conceptually odd if you only ever ship Windows.
Best use:
Canvas-first app, custom rendering, tablet/controller/input handling,
future audio/video/game-like interaction.
For your Steam context, SDL3 is a serious option. For a classic desktop app with native menus, I would hesitate.
Option 5: raylib + raygui
raylib has a friendly C API and a Zig ecosystem. Search results show Zig/raylib/raygui bindings and examples, including bindings that translate raylib.h, rlgl.h, rcamera.h, and raygui.h to Zig and include build tooling. [GitHub] There are also recent community examples of using raylib + raygui with Zig. [Ziggit]
This is more “game/app canvas framework” than “Windows GUI wrapper”.
Pros
Very simple drawing model.
Good for a Paint clone.
C API is Zig-friendly.
raygui gives you basic immediate UI.
Cons
Not native Windows UI.
May pull in graphics-stack concerns you initially avoided.
Menus/dialogs are app-drawn unless you mix Win32.
Less ideal for complex document-app polish.
Best use:
Toy/prototype paint app, game-like editor, custom canvas UI.
If the app is meant to become a serious desktop productivity app, I’d prefer Dear ImGui or a custom Win32 wrapper over raygui. If the app is meant to remain playful/tool-like, raylib is attractive.
Option 6: libui-ng / zig-libui-ng
libui-ng is a C library for portable GUIs using each platform’s native GUI technologies; its repository describes it as simple and portable, using native GUI technologies on supported platforms. [GitHub] There are Zig bindings, zig-libui-ng, described as WIP bindings and a manual cleanup of ui.h cimport. [GitHub]
This is interesting if you want native buttons, boxes, text fields, menus, etc.
But I’d be careful. The libui-ng old-news page still describes libui as mid-alpha, with missing features and stability caveats. [libui-ng.github.io] For a small app, okay. For your long-lived “Steam Windows binary under Proton” idea, I would not bet the architecture on it yet.
Pros
C API.
Native-ish controls.
Zig bindings exist.
Much simpler than raw Win32 for normal widgets.
Cons
Maturity concerns.
Canvas/custom drawing may be limiting.
Bindings are WIP.
Long-term project risk.
Best use:
Small native-form-style utilities.
Less ideal for a drawing app whose center is a custom canvas.
Option 7: wxWidgets / Qt / bigger C++ GUI toolkits
wxWidgets is mature and open-source; it describes itself as a C++ library for Windows, macOS, Linux, and other platforms with one codebase. [wxwidgets.org] Its GitHub description says it is a cross-platform C++ framework for advanced GUI apps using native controls. [GitHub]
But from Zig, this means C++ interop or a C wrapper. That is possible, but it adds an integration layer you will own.
Qt is similar but larger and more licensing/build/distribution-heavy. You did not ask for Qt specifically, and for a Windows .exe under Proton I would avoid dragging in a huge framework unless you really want a full desktop app framework.
Pros
Mature desktop app facilities.
Native-ish widgets.
Menus, dialogs, layout, documents, etc.
Cons
C++ integration from Zig is nontrivial.
Large dependency/bundling surface.
More Proton test matrix.
Can dominate your architecture.
Best use:
You decide the GUI layer matters more than keeping Zig close to the OS.
At that point I’d honestly ask whether the GUI shell should be C++/Qt or C++/wxWidgets, with Zig used for the core engine/library.
Option 8: IUP
IUP is a portable GUI toolkit with a C API; its site describes it as a multi-platform toolkit for GUI apps with a simple API in C, Lua, and LED. [Tecgraf] It is obscure compared with SDL/ImGui/wx/Qt, but C API plus portability makes it technically plausible.
Pros
C API.
Designed for portable GUI.
Potentially easier from Zig than C++ frameworks.
Cons
Smaller ecosystem.
Less common in Steam/game-adjacent software.
You will need to test Proton behavior yourself.
Long-term availability/community risk.
I would only choose this if you like its API after a spike.
What I would not do first
I would not jump to GTK. You are shipping a Windows executable, so GTK-on-Windows through Proton adds a lot of alien machinery.
I would not use WPF/.NET/WebView2 for this Proton-first path. Valve’s Proton docs specifically call out .NET/WPF as a trouble area and recommend standalone technologies like Qt for launchers or avoiding launchers altogether. [GitHub]
I would not over-index on native Windows controls if the core app is a drawing canvas. Menus, file dialogs, message boxes, and maybe common dialogs can stay native via a small wrapper; the canvas/tool UI can be custom.
Decision table
| Path | Good fit? | Why |
|---|
zigwin32 + your own wrappers | Excellent first step | Reduces raw binding pain without changing runtime behavior |
| Dear ImGui | Excellent for tool UI | Mature immediate-mode UI, strong backend ecosystem |
| Nuklear | Good | Smaller C immediate-mode UI, but less ecosystem polish |
| SDL3 + ImGui/custom UI | Good | Hides window/input, good Steam-ish fit, but not native GUI |
| raylib + raygui | Good for playful/custom app | Very simple canvas/rendering model |
| libui-ng | Maybe | Native-ish C GUI, but maturity risk |
| wxWidgets/Qt | Maybe later | Mature, but C++/large dependency burden |
| Raw Win32 forever | Bad for growth | You already know why |
My concrete architectural suggestion
Keep the current app as the “known-good Proton baseline”, then do this:
src/
main.zig // tiny entry point
platform/win32_app.zig // message loop, HWND, menus, dialogs
platform/win32_gdi.zig // pens, brushes, DC wrappers, canvas helpers
app/app.zig // app state, commands, drawing model
app/canvas.zig // pencil strokes / ephemeral drawing logic
Use zigwin32 underneath the platform layer.
Expose your own Zig API like:
pub const AppEvent = union(enum) {
paint: PaintContext,
resize: Size,
mouse_down: Point,
mouse_move: Point,
mouse_up: Point,
command: Command,
close_requested,
};
pub fn run(app: *App) !void;
Then, when you add a UI toolkit, you can put it behind the same boundary.
For the next real UI step, I’d choose between:
A. Dear ImGui if you want fast-growing tool UI.
B. Stay custom Win32 wrappers if you want native menus/dialogs and minimal dependencies.
C. SDL3 + Dear ImGui if you want to stop caring about HWND/WndProc entirely.
For your specific “Windows binary, Linux via Proton, Zig, simple drawing app” context, my strongest recommendation is:
Now: zigwin32 + your own thin Zig framework.
Soon: Dear ImGui for non-canvas UI if the app starts needing panels/tools/settings.
Avoid: heavyweight native GUI frameworks until the app proves it needs them.