On this page
Windows
deno desktop ships in Deno v2.9.0 and is not in a stable release yet. To try
it now, run deno upgrade canary to install the
canary build. The command, configuration
keys, and TypeScript APIs may still change before the feature is stable.
The Deno.BrowserWindow class controls native
windows. A window opens automatically when your binary starts and is navigated
to your local HTTP server. The first
new Deno.BrowserWindow() you construct adopts that initial window; every
construction after that opens a new one. All windows share the same Deno
runtime: there is one async runtime per process, regardless of how many windows
are open.
Creating windows Jump to heading
// The first construction adopts the implicit startup window.
const win = new Deno.BrowserWindow({ title: "My App" });
// Subsequent constructions open additional windows.
const base = Deno.env.get("DENO_SERVE_ADDRESS")!; // "tcp:127.0.0.1:<port>"
const port = base.split(":").pop();
const settings = new Deno.BrowserWindow({
title: "Settings",
width: 420,
height: 320,
});
settings.navigate(`http://127.0.0.1:${port}/settings`);
The constructor accepts a BrowserWindowOptions object:
| Option | Type | Default | Notes |
|---|---|---|---|
title |
string |
none | Window title. |
width |
number |
800 |
Initial width in logical pixels. |
height |
number |
600 |
Initial height in logical pixels. |
x, y |
number |
none | Initial position; centered if omitted. |
resizable |
boolean |
true |
Whether the user can resize the window. |
alwaysOnTop |
boolean |
false |
Keep the window above others. |
frameless |
boolean |
false |
Remove the title bar and window chrome. Creation-only. |
noActivate |
boolean |
false |
Floating, non-activating panel that doesn't steal focus. Creation-only. |
transparentTitlebar |
boolean |
false |
Blend the title bar into the content. Creation-only. |
new Deno.BrowserWindow() opens (or adopts) a window immediately. The window is
alive until close() is called or the user closes it from the OS.
frameless, noActivate, and transparentTitlebar can only be set at creation
time. frameless + noActivate together are the building blocks for tray /
menu-bar popovers; see Tray.attachPanel.
Multiple windows are independent: each has its own size, position, focus state, and webview. They can navigate to different paths or different origins, set their own bindings, and emit their own events.
Lifecycle Jump to heading
win.show();
win.hide();
win.focus();
win.close(); // sends close request, fires "close" event
win.reload(); // reload the webview's current document
if (win.isClosed()) { /* … */ }
if (win.isVisible()) { /* … */ }
Each window has a stable numeric id:
console.log(win.windowId);
Closing a window does not stop the runtime; the process keeps running until all
windows are closed (or you call Deno.exit()).
Size and position Jump to heading
const [w, h] = win.getSize();
win.setSize(800, 600);
const [x, y] = win.getPosition();
win.setPosition(100, 100);
if (win.isResizable()) { /* … */ }
win.setResizable(false);
if (win.isAlwaysOnTop()) { /* … */ }
win.setAlwaysOnTop(true);
Sizes are in logical pixels. The OS handles HiDPI scaling.
Title Jump to heading
win.setTitle("My App: Untitled");
Use a stable prefix plus a document-specific suffix; this is what users see in window switchers, the dock, and the taskbar.
Navigation Jump to heading
const port = Deno.env.get("DENO_SERVE_ADDRESS")!.split(":").pop();
win.navigate(`http://127.0.0.1:${port}`);
Navigation works with any URL the embedded webview can load, most commonly the
local HTTP server (see HTTP serving), but also
https:// URLs, file:// URLs, and data: URLs.
For multi-page apps, use the local HTTP server's routing rather than swapping windows. For modal dialogs, prefer creating a child window over navigating away.
Events Jump to heading
Deno.BrowserWindow is an EventTarget.
Listen with addEventListener or assign to the matching on<event> property.
win.addEventListener("resize", (e) => {
console.log("resized to", e.detail.width, e.detail.height);
});
win.onfocus = () => console.log("focused");
win.onblur = () => console.log("blurred");
| Event | When it fires |
|---|---|
resize |
The window's size changed. |
move |
The window's position changed. |
focus |
The window gained focus. |
blur |
The window lost focus. |
close |
The user requested the window close. |
keydown |
A key was pressed while the window was focused. |
keyup |
A key was released. |
mousemove |
The pointer moved over the window. |
mouseenter |
The pointer entered the window. |
mouseleave |
The pointer left the window. |
mousedown |
A mouse button was pressed. |
mouseup |
A mouse button was released. |
click |
A mouse click landed on the window. |
dblclick |
A double-click landed on the window. |
wheel |
A scroll wheel / trackpad scroll happened. |
menuclick |
An application-menu item was clicked. |
contextmenuclick |
A context-menu item was clicked. |
The pointer and keyboard events mirror their browser equivalents
(KeyboardEvent, MouseEvent, WheelEvent). resize, move, menuclick,
and contextmenuclick are CustomEvents carrying a detail payload; see
Menus for the menu events.
win.addEventListener("keydown", (e) => {
if (e.key === "Escape") win.close();
});
Running JavaScript in the webview Jump to heading
const result = await win.executeJs(
"document.querySelectorAll('li').length",
);
console.log(result); // number of <li> on the current page
executeJs runs the code in the webview's main world and resolves with the
result. The value crosses a realm boundary, so it must be JSON-serializable; if
the script throws, the returned promise rejects with the thrown value.
For richer Deno ↔ webview communication, use bindings instead.
Native window handle Jump to heading
const surface = win.getNativeWindow();
getNativeWindow() wraps the window's native surface as a
Deno.UnsafeWindowSurface so you can
render to it with WebGPU. Request a GPU adapter first; the call throws if there
is no active WebGPU context:
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter!.requestDevice();
const surface = win.getNativeWindow();
const context = surface.getContext("webgpu");
// configure `context` with `device` and draw…
Once a surface has been taken, close() is downgraded to hide() so the native
handles backing the surface are not destroyed out from under WebGPU.
DevTools Jump to heading
win.openDevtools(); // both isolates
win.openDevtools({ deno: false }); // renderer only
win.openDevtools({ renderer: false }); // Deno runtime only
See DevTools.
Closing the app Jump to heading
The runtime exits when no windows are open and there are no other live async tasks (timers, pending fetches, etc.). To exit explicitly:
Deno.exit(0);
To prevent close (for example, to show a "Save?" dialog), listen for close and
call event.preventDefault():
win.addEventListener("close", async (e) => {
if (hasUnsavedChanges) {
e.preventDefault();
const ok = await win.executeJs("confirm('Discard changes?')");
if (ok) win.close();
}
});