Draggable Regions
Electrobun’s injected preload turns designated DOM regions into native window
drag handles. No Electroview instance or RPC setup is required for dragging
itself.
Use the compatibility classes:
<header class="titlebar electrobun-webkit-app-region-drag"> <strong>My App</strong> <nav class="window-controls electrobun-webkit-app-region-no-drag"> <button id="minimize" type="button" aria-label="Minimize">_</button> <button id="maximize" type="button" aria-label="Maximize">+</button> <button id="close" type="button" aria-label="Close">x</button> </nav></header>Or use an app-region CSS property. Electrobun recognizes
-webkit-app-region, app-region, and the legacy window-drag spelling in
inline and same-origin stylesheets.
.titlebar { -webkit-app-region: drag; app-region: drag; align-items: center; display: flex; height: 36px; user-select: none;}
.window-controls,.window-controls button { -webkit-app-region: no-drag; app-region: no-drag;}A no-drag descendant always wins over a draggable ancestor. Apply it to
buttons, inputs, links, menus, and every other interactive control inside the
titlebar.
Window Configuration
Use titleBarStyle: "hidden" for fully custom chrome. hiddenInset retains
the inset native controls on macOS.
import { BrowserWindow } from "electrobun/main";
const win = new BrowserWindow({ title: "Custom Titlebar", url: "views://mainview/index.html", frame: { width: 800, height: 600 }, titleBarStyle: "hidden",});
void win;Custom Window Controls
Custom HTML buttons need typed RPC to invoke native window operations. Define the same schema in both contexts. In a project, place this type in a shared module rather than duplicating it.
Main process:
import { BrowserView, BrowserWindow, type RPCSchema,} from "electrobun/main";
type WindowControlsRPC = { bun: RPCSchema<{ requests: {}; messages: { closeWindow: void; minimizeWindow: void; maximizeWindow: void; }; }>; webview: RPCSchema<{ requests: {}; messages: {} }>;};
let win: BrowserWindow;const rpc = BrowserView.defineRPC<WindowControlsRPC>({ handlers: { requests: {}, messages: { closeWindow: () => win.close(), minimizeWindow: () => win.minimize(), maximizeWindow: () => { if (win.isMaximized()) win.unmaximize(); else win.maximize(); }, }, },});
win = new BrowserWindow({ title: "Custom Titlebar", url: "views://mainview/index.html", frame: { width: 800, height: 600 }, titleBarStyle: "hidden", rpc,});Browser view:
import { Electroview, type RPCSchema } from "electrobun/view";
type WindowControlsRPC = { bun: RPCSchema<{ requests: {}; messages: { closeWindow: void; minimizeWindow: void; maximizeWindow: void; }; }>; webview: RPCSchema<{ requests: {}; messages: {} }>;};
const rpc = Electroview.defineRPC<WindowControlsRPC>({ handlers: { requests: {}, messages: {} },});new Electroview({ rpc });
document.querySelector("#close")?.addEventListener("click", () => { rpc.send.closeWindow();});document.querySelector("#minimize")?.addEventListener("click", () => { rpc.send.minimizeWindow();});document.querySelector("#maximize")?.addEventListener("click", () => { rpc.send.maximizeWindow();});The RPC calls use the local rpc object returned by defineRPC. Constructing
Electroview attaches its transport to that object.
Stylesheet Notes
Electrobun mirrors app-region declarations into a custom property so system webviews that discard the vendor property still preserve CSS cascade. Inline styles and same-origin stylesheets are supported. A cross-origin stylesheet that cannot be fetched may depend on what the current renderer exposes through computed style; keep titlebar rules in application-owned CSS.