How to Implement Context Menus, Dialogs, and System Notifications in the Native SDK

You can implement context menus, dialogs, and system notifications in the Native SDK by importing the runtime widgets in src/runtime/canvas_widget_context_menu.zig, src/runtime/canvas_widget_dialog.zig, and src/runtime/system_notification.zig, then initializing their Zig structs with items, callbacks, and content panes.

The vercel-labs/native repository is a cross-platform UI framework written in Zig that lets you build native desktop applications using canvas primitives and declarative widgets. If you want to implement context menus, dialogs, and system notifications in the Native SDK, you will work primarily with the runtime widget layer located in src/runtime/. This article walks through the exact file paths, APIs, and code patterns used to add interactive menus, modal overlays, and OS-level notifications to your app.

Native SDK UI Architecture

Canvas Primitives Layer

The lowest layer exposes a platform-agnostic drawing API for paths, text, and icons. These canvas primitives live in src/primitives/canvas and provide the rendering foundation that all higher-level widgets consume.

Runtime Widget Layer

Concrete interactive components that combine canvas drawing with mouse and keyboard handling live in src/runtime/. This layer manages state, boundary clamping, and event emission for components such as context menus and dialogs.

App Manifest Layer

Applications declare how widgets fit together through a small declarative manifest. The app.zon files inside examples/*/app.zon wire widgets to the event loop and define the main application entry point.

Implementing Context Menus in the Native SDK

Context menus are implemented in src/runtime/canvas_widget_context_menu.zig. According to the vercel-labs/native source code, the widget performs four core tasks:

  1. Registers a right-click or long-press handler on any canvas element.
  2. Builds a vertical list of selectable items using canvas text and icon primitives.
  3. Positions itself automatically based on the click point, clamping to window bounds.
  4. Emits a MenuSelect event that the application listens to and acts upon.

The public API is a small struct:

pub const ContextMenu = struct {
    items: []const MenuItem,
    onSelect: fn (selected: usize) void,
    // ...internal state
};

A MenuItem holds a label and an optional icon sourced from src/primitives/canvas/icons/menu.svg. The onSelect callback receives the index of the chosen entry.

Here is a minimal example that attaches a context menu to the root canvas:

// src/main.zig
const std = @import("std");
const native = @import("native");
const ContextMenu = @import("runtime/canvas_widget_context_menu.zig").ContextMenu;

pub fn main() void {
    var app = native.App.init(.{
        .title = "Context-Menu Demo",
        .width = 400,
        .height = 300,
    });

    const items = [_]ContextMenu.MenuItem{
        .{ .label = "Copy", .icon = null },
        .{ .label = "Paste", .icon = null },
        .{ .label = "Delete", .icon = null },
    };

    app.canvas.setContextMenu(ContextMenu{
        .items = &items,
        .onSelect = onMenuSelect,
    });

    app.run();
}

fn onMenuSelect(selected: usize) void {
    const labels = [_][]const u8{ "Copy", "Paste", "Delete" };
    std.debug.print("User selected: {s}\n", .{labels[selected]});
}

When the user right-clicks the canvas, the menu appears and selecting an item prints the choice.

Creating Modal Dialogs in the Native SDK

Modal dialogs are built on top of the same canvas widget system. The dialog component in src/runtime/canvas_widget_dialog.zig composes three visual elements:

  • A background overlay that captures clicks outside the dialog to dismiss it.
  • A content pane that hosts arbitrary canvas widgets such as text, inputs, and buttons.
  • Action buttons (for example, "OK" and "Cancel") that emit a DialogResult event.

Typical usage involves creating a Dialog instance, populating its content list, and supplying callbacks for each button. The dialog automatically centers itself and disables interaction with underlying widgets while open.

The following example shows a save confirmation dialog:

const Dialog = @import("runtime/canvas_widget_dialog.zig").Dialog;

fn showSaveDialog(app: *native.App) void {
    var dlg = Dialog{
        .title = "Save File",
        .content = &[_]Dialog.Content{
            .{ .type = .Text, .value = "Enter filename:" },
            .{ .type = .Input, .value = "" },
        },
        .buttons = &[_]Dialog.Button{
            .{ .label = "Cancel", .role = .Cancel, .onPress = onCancel },
            .{ .label = "Save",   .role = .Primary, .onPress = onSave },
        },
    };
    app.showDialog(&dlg);
}

fn onCancel() void {
    std.debug.print("Dialog cancelled\n", .{});
}

fn onSave() void {
    std.debug.print("Proceed with save operation\n", .{});
}

The dialog blocks interaction with the rest of the UI until a button is pressed.

Sending System Notifications in the Native SDK

Native-level system notifications are abstracted in src/runtime/system_notification.zig. As implemented in vercel-labs/native, the SDK translates a high-level Notification struct into platform-specific APIs:

  • macOS – NSUserNotification via the Objective-C bridge.
  • Windows – Win32 toast notification APIs.
  • Linux – D-Bus org.freedesktop.Notifications messages.

The API is straightforward:

pub const Notification = struct {
    title: []const u8,
    body: []const u8,
    iconPath: ?[]const u8,
    // optional callback for click action
    onClick: ?fn () void,
};

pub fn push(notif: Notification) void;

Calling push schedules the notification with the host OS. The SDK handles permission requests and fallback behavior on platforms that lack native support.

Here is an example that pushes a welcome notification:

const Notification = @import("runtime/system_notification.zig").Notification;

fn pushWelcome(app: *native.App) void {
    const note = Notification{
        .title = "Welcome!",
        .body = "Your Native app is running.",
        .iconPath = null,
        .onClick = onNotificationClick,
    };
    Notification.push(note);
}

fn onNotificationClick() void {
    std.debug.print("User clicked the notification\n", .{});
}

When triggered, this displays a native OS notification, and tapping it runs onNotificationClick.

Summary

  • src/runtime/canvas_widget_context_menu.zig provides the context menu widget that binds to right-click events and returns a selected index through a callback.
  • src/runtime/canvas_widget_dialog.zig implements modal dialogs with an overlay, content pane, and configurable action buttons that emit result events.
  • src/runtime/system_notification.zig exposes a cross-platform notification API that maps to macOS, Windows, and Linux native backends.
  • All three features rely on the canvas primitives in src/primitives/canvas and are wired together through the app manifest pattern found in examples/*/app.zon.

Frequently Asked Questions

How do I attach a context menu to a specific canvas element?

You initialize a ContextMenu struct with an array of MenuItem entries and an onSelect callback, then attach it via app.canvas.setContextMenu(). The widget in src/runtime/canvas_widget_context_menu.zig registers the right-click handler and positions the menu automatically.

Can I customize the buttons and layout inside a Native SDK dialog?

Yes. The Dialog struct defined in src/runtime/canvas_widget_dialog.zig exposes a content slice that accepts arbitrary canvas widgets including text and input fields. You define buttons in the buttons slice and assign each a role and onPress callback.

Which platforms support system notifications out of the box?

The Native SDK supports system notifications on macOS, Windows, and Linux. The src/runtime/system_notification.zig module translates the generic Notification struct into NSUserNotification, Win32 toasts, or D-Bus messages depending on the host OS.

Where are the canvas primitives for drawing UI elements defined?

Low-level drawing primitives live in src/primitives/canvas. These provide the platform-agnostic paths, text rendering, and icon utilities that both canvas_widget_context_menu.zig and canvas_widget_dialog.zig consume to render their interfaces.

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 →