dlang-supplemental

Blog

Hosting Avalonia from D

17 August 2026 · rehash of the docs essay

Avalonia is not a widget catalog in the Qt or GTK sense. Open a fresh project and you get panels, a button that is still a button, a text box that is still a text box, and a styling system that feels like writing HTML and CSS with extra steps. That is a compliment to the engine and a warning to anyone who hoped to import avalonia and receive a dashboard.

If you have hand-hacked Avalonia, you already know the texture. A StackPanel is a div with a flex direction. Classes.Add("Primary") is how you ask the theme for a filled button. There is no blessed settings shell waiting in the box. You compose, or you look elsewhere.

That “elsewhere” already exists in C#. ShadUI is a living MIT library inspired by shadcn/ui and Suki UI. It ships ShadTheme, a custom window, cards, toasts, dialogs, a sidebar—the kit people reach for when Avalonia’s primitives feel thin. Cloning shadcn again, inside Avalonia, from D, would be a second copy of a problem someone already solved on the C# side of the wall.

The wall

D cannot see C# types. There is no extern (C#). Avalonia has no C API. So a “wrapper” that pretends GtkD happened—generate thousands of classes from metadata, link, go—does not exist here.

What exists is hosting: a D main that loads a native shim, the shim that boots CoreCLR, and a C# library that owns the Application and the handle table. avalonia-d takes that shape on purpose.

The C# host starts Avalonia in code, adds ShadTheme, and subclasses ShadUI.Window because the upstream constructor is protected. DNNE exports a small C ABI. D dynamically loads the shim the same way other supplemental bindings load a Zig core or a Rust bridge.

Handles are ulong. Callbacks are extern (C) function pointers. Forget that qualifier on the D aliases and the fourth double in a margin call arrives as NaN. That is an interop lesson, not a theming lesson.

What you write

import avalonia;

void main()
{
    init(ThemeMode.dark);

    auto win = new Window("Hello", 480, 280);
    auto col = new Stack();
    col.margin(24, 24, 24, 24);

    auto btn = new Button("Click me");
    btn.onClick({ /* ... */ });
    col.add(btn);

    win.content = col;
    win.run();
}

D owns the process. Avalonia owns the pixels. ShadUI owns the look. You do not get every Avalonia type. You get a growing C ABI and idiomatic wrappers for the controls that have been walked across the boundary.

Fork ShadUI under this org when a C# control has to change. Until then, consume the NuGet and spend the D budget on the host and the handles. The interesting remaining work is more of the ShadUI surface on the ABI— dialogs, toasts, sidebar—without pretending the C# object graph is now a D object graph.

Read next: