Multiple WebViews

Zero models each native window as a stack of named WebViews. A WebView can fill the window, cover a panel-sized region, or sit above another WebView by setting its frame and layer.

This is the right model for browser chrome, dashboards, previews, auth flows, and any app that needs trusted UI next to isolated page content.

The startup app source is the default trusted WebView named main. Simple apps can ignore this and keep using one page. Advanced apps can resize main and place isolated child WebViews beneath or above it. main is reserved; child WebViews cannot be created with that label.

JavaScript API

Enable the built-in WebView helpers from an allowed origin:

const app_permissions = [_][]const u8{zero_native.security.permission_window};

.security = .{
    .permissions = &app_permissions,
    .navigation = .{ .allowed_origins = &.{ "zero://app", "https://example.com" } },
},
.js_window_api = true,

Then create and manage WebViews from JavaScript:

const preview = await window.zero.webviews.create({
  label: "preview",
  url: "https://example.com",
  frame: { x: 24, y: 24, width: 480, height: 320 },
  layer: 10,
  transparent: false,
  bridge: false,
});

await preview.setFrame({ x: 32, y: 32, width: 640, height: 420 });
await preview.navigate("https://example.com/docs");
await preview.setZoom(1.25);
await preview.setLayer(20);
await preview.close();

Coordinates are logical content coordinates relative to the parent window: x: 0, y: 0 is the top-left corner of the window content area.

layer controls native stacking. Higher layers appear above lower layers. DOM z-index only affects elements inside one WebView; it cannot place one WebView above another.

transparent: true requests a transparent native WebView background for chrome and menu surfaces. Support is backend dependent and best effort.

bridge: true injects window.zero into that WebView. Use it only for trusted app chrome. Leave it false for untrusted page content.

windowId defaults to the window that calls the command. If you pass it explicitly, it must match the calling window; one window cannot manage another window's WebViews.

webviews.list() includes main first, followed by open child WebViews in the calling window. main reports the current startup WebView frame, layer, bridge state, and source URL.

You can also enable the helpers with an explicit builtin_bridge policy instead of js_window_api. In that mode, list every WebView command your app calls: zero-native.webview.create, zero-native.webview.list, zero-native.webview.setFrame, zero-native.webview.navigate, zero-native.webview.setZoom, zero-native.webview.setLayer, and zero-native.webview.close.

Lifecycle

WebView labels are unique per parent window. Creating a second WebView with the same label fails; close the existing WebView first if you want to replace it.

The main WebView can be resized and zoomed through setFrame({ label: "main", ... }) and setZoom({ label: "main", ... }) on supported backends. It cannot be closed or replaced. Layering main is backend dependent; use child WebView layers for portable stacking.

When a parent window closes, its child WebViews are closed with it. Child WebViews do not receive window.zero by default, so untrusted content cannot call native bridge commands through the app bridge.

WebView slots are capped globally per runtime, not per window. The default cap is 16 child WebViews.

Commands

The helper calls these built-in bridge commands internally:

HelperCommandDescription
window.zero.webviews.create(options) / previewzero-native.webview.createCreate a named WebView in a parent window
window.zero.webviews.list()zero-native.webview.listList WebViews in the calling window
window.zero.webviews.setFrame(options) / preview.setFrame(frame)zero-native.webview.setFrameMove or resize a WebView
window.zero.webviews.navigate(options) / preview.navigate(url)zero-native.webview.navigateNavigate a WebView to a new URL
window.zero.webviews.setZoom(options) / preview.setZoom(zoom)zero-native.webview.setZoomSet page zoom from 0.25 to 5.0
window.zero.webviews.setLayer(options) / preview.setLayer(layer)zero-native.webview.setLayerChange native stack order
window.zero.webviews.close(options) / preview.close()zero-native.webview.closeClose a WebView

WebView URLs are checked against the runtime navigation policy before native views are created or navigated. Add the target origin to security.navigation.allowed_origins before loading it.

Backend Support

System WebView stacks are implemented on macOS and Linux with layers, best-effort transparency, bridge-enabled trusted child WebViews, targeted bridge responses, and page zoom. Windows WebView2 child WebViews are in progress: child layers and navigation are available when WebView2 is installed, but bridge-enabled child WebViews and main WebView frame/zoom/layer operations reject with explicit unsupported-backend errors. macOS CEF supports layered child WebViews, page zoom, and bridge-enabled trusted child WebViews. Linux CEF explicitly rejects child WebView creation until native CEF embedding is wired in.