electrobun-native-ui
SKILL.md
Electrobun Native UI
Comprehensive guide to native UI integration in Electrobun applications.
Application Menu
Basic Menu Structure
import { ApplicationMenu } from "electrobun/bun";
ApplicationMenu.setMenu([
{
label: "File",
submenu: [
{
label: "New",
accelerator: "CmdOrCtrl+N",
action: () => createNewDocument()
},
{
label: "Open...",
accelerator: "CmdOrCtrl+O",
action: () => openFile()
},
{ type: "separator" },
{
label: "Save",
accelerator: "CmdOrCtrl+S",
action: () => saveFile()
},
{
label: "Save As...",
accelerator: "CmdOrCtrl+Shift+S",
action: () => saveFileAs()
},
{ type: "separator" },
{
label: "Quit",
accelerator: "CmdOrCtrl+Q",
action: () => app.quit()
}
]
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ type: "separator" },
{ role: "selectAll" }
]
},
{
label: "View",
submenu: [
{
label: "Reload",
accelerator: "CmdOrCtrl+R",
action: () => currentWindow.reload()
},
{
label: "Toggle DevTools",
accelerator: "CmdOrCtrl+Shift+I",
action: () => currentWindow.toggleDevTools()
},
{ type: "separator" },
{
label: "Zoom In",
accelerator: "CmdOrCtrl+Plus",
action: () => adjustZoom(0.1)
},
{
label: "Zoom Out",
accelerator: "CmdOrCtrl+-",
action: () => adjustZoom(-0.1)
},
{
label: "Reset Zoom",
accelerator: "CmdOrCtrl+0",
action: () => resetZoom()
}
]
},
{
label: "Help",
submenu: [
{
label: "Documentation",
action: () => shell.openExternal("https://docs.myapp.com")
},
{ type: "separator" },
{
label: "About",
action: () => showAboutDialog()
}
]
}
]);
Built-in Roles
// Standard edit roles
{ role: "undo" } // Undo last action
{ role: "redo" } // Redo last action
{ role: "cut" } // Cut selection
{ role: "copy" } // Copy selection
{ role: "paste" } // Paste from clipboard
{ role: "selectAll" } // Select all content
// Window roles
{ role: "minimize" } // Minimize window
{ role: "close" } // Close window
{ role: "quit" } // Quit application
{ role: "reload" } // Reload current page
{ role: "forceReload" } // Force reload (clear cache)
{ role: "toggleDevTools" } // Toggle developer tools
{ role: "toggleFullScreen" } // Toggle fullscreen
// Zoom roles
{ role: "zoomIn" } // Zoom in
{ role: "zoomOut" } // Zoom out
{ role: "resetZoom" } // Reset zoom to 100%
Dynamic Menus
class MenuManager {
private currentMenu: any[] = [];
updateRecentFiles(files: string[]) {
const recentMenu = files.map(file => ({
label: path.basename(file),
action: () => openFile(file)
}));
// Find File menu and update Recent submenu
const fileMenu = this.currentMenu.find(m => m.label === "File");
const recentIndex = fileMenu.submenu.findIndex(
(item: any) => item.label === "Recent"
);
if (recentIndex >= 0) {
fileMenu.submenu[recentIndex] = {
label: "Recent",
submenu: recentMenu.length > 0 ? recentMenu : [
{ label: "No recent files", enabled: false }
]
};
}
ApplicationMenu.setMenu(this.currentMenu);
}
setCheckState(menuPath: string[], checked: boolean) {
// Navigate menu structure and update check state
let current: any = this.currentMenu;
for (let i = 0; i < menuPath.length - 1; i++) {
current = current.find((m: any) => m.label === menuPath[i]);
if (!current) return;
current = current.submenu;
}
const item = current.find((m: any) => m.label === menuPath[menuPath.length - 1]);
if (item) {
item.checked = checked;
ApplicationMenu.setMenu(this.currentMenu);
}
}
enableMenuItem(menuPath: string[], enabled: boolean) {
// Similar navigation to setCheckState
// Set item.enabled = enabled
}
}
// Usage
const menuManager = new MenuManager();
menuManager.updateRecentFiles(["/path/to/file1.txt", "/path/to/file2.txt"]);
menuManager.setCheckState(["View", "Show Sidebar"], true);
Platform-Specific Menus
function createMenu() {
const isMac = process.platform === "darwin";
const menu = [];
// macOS app menu
if (isMac) {
menu.push({
label: app.name,
submenu: [
{ role: "about" },
{ type: "separator" },
{
label: "Preferences...",
accelerator: "Cmd+,",
action: () => showPreferences()
},
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" }
]
});
}
// File menu
menu.push({
label: "File",
submenu: [
{ label: "New", accelerator: "CmdOrCtrl+N", action: createNew },
{ label: "Open", accelerator: "CmdOrCtrl+O", action: openFile },
// On Windows/Linux, add Quit here
...(!isMac ? [
{ type: "separator" },
{ label: "Exit", action: () => app.quit() }
] : [])
]
});
return menu;
}
Context Menu
Basic Context Menu
import { ContextMenu, BrowserWindow } from "electrobun/bun";
const win = new BrowserWindow({ /* ... */ });
win.on("context-menu", (event) => {
ContextMenu.show([
{
label: "Copy",
accelerator: "CmdOrCtrl+C",
action: () => {
// Copy selected text
}
},
{
label: "Paste",
accelerator: "CmdOrCtrl+V",
action: () => {
// Paste from clipboard
}
},
{ type: "separator" },
{
label: "Inspect Element",
action: () => {
win.inspectElement(event.x, event.y);
}
}
]);
});
Dynamic Context Menu
// Webview sends context info
// In webview (index.ts):
document.addEventListener("contextmenu", (e) => {
e.preventDefault();
const target = e.target as HTMLElement;
const context = {
x: e.clientX,
y: e.clientY,
hasSelection: window.getSelection()?.toString().length > 0,
isLink: target.tagName === "A",
linkUrl: target.tagName === "A" ? (target as HTMLAnchorElement).href : null,
isImage: target.tagName === "IMG",
imageUrl: target.tagName === "IMG" ? (target as HTMLImageElement).src : null,
};
electroview.rpc.showContextMenu(context);
});
// Main process handles it
win.defineRpc({
handlers: {
async showContextMenu(context: any) {
const menu = [];
if (context.hasSelection) {
menu.push(
{ label: "Copy", action: () => win.rpc.copy() },
{ type: "separator" }
);
}
if (context.isLink) {
menu.push(
{
label: "Open Link",
action: () => shell.openExternal(context.linkUrl)
},
{
label: "Copy Link",
action: () => clipboard.write(context.linkUrl)
},
{ type: "separator" }
);
}
if (context.isImage) {
menu.push(
{
label: "Save Image...",
action: async () => {
const result = await dialog.showSaveDialog({
defaultPath: "image.png"
});
if (!result.canceled) {
await downloadImage(context.imageUrl, result.filePath);
}
}
},
{
label: "Copy Image",
action: () => copyImage(context.imageUrl)
},
{ type: "separator" }
);
}
menu.push({
label: "Inspect",
action: () => win.inspectElement(context.x, context.y)
});
ContextMenu.show(menu);
}
}
});
System Tray
Basic Tray Icon
import { Tray } from "electrobun/bun";
const tray = new Tray({
icon: "assets://tray-icon.png",
tooltip: "My Application",
menu: [
{
label: "Show Window",
action: () => mainWindow.show()
},
{
label: "Hide Window",
action: () => mainWindow.hide()
},
{ type: "separator" },
{
label: "Quit",
action: () => app.quit()
}
]
});
Dynamic Tray Updates
class TrayManager {
private tray: Tray;
private status: "idle" | "working" | "error" = "idle";
constructor() {
this.tray = new Tray({
icon: this.getIconForStatus("idle"),
tooltip: "My App - Idle"
});
this.updateMenu();
}
setStatus(status: "idle" | "working" | "error") {
this.status = status;
this.tray.setIcon(this.getIconForStatus(status));
this.tray.setTooltip(`My App - ${status}`);
this.updateMenu();
}
private getIconForStatus(status: string) {
return `assets://tray-${status}.png`;
}
private updateMenu() {
const menu = [
{
label: `Status: ${this.status}`,
enabled: false
},
{ type: "separator" },
{
label: "Show Window",
action: () => mainWindow.show()
}
];
if (this.status === "working") {
menu.push({
label: "Cancel",
action: () => cancelWork()
});
}
menu.push(
{ type: "separator" },
{
label: "Quit",
action: () => app.quit()
}
);
this.tray.setMenu(menu);
}
showNotification(title: string, message: string) {
this.tray.showBalloon({
title,
content: message,
icon: "assets://notification-icon.png"
});
}
}
const trayManager = new TrayManager();
trayManager.setStatus("working");
trayManager.showNotification("Task Complete", "Your task has finished");
Native Dialogs
File Dialogs
import { dialog } from "electrobun/bun";
// Open single file
async function openFile() {
const result = await dialog.showOpenDialog({
title: "Open File",
defaultPath: paths.home,
filters: [
{ name: "Text Files", extensions: ["txt", "md"] },
{ name: "All Files", extensions: ["*"] }
],
properties: ["openFile"]
});
if (!result.canceled && result.filePaths.length > 0) {
const content = await Bun.file(result.filePaths[0]).text();
return { path: result.filePaths[0], content };
}
return null;
}
// Open multiple files
async function openMultipleFiles() {
const result = await dialog.showOpenDialog({
properties: ["openFile", "multiSelections"]
});
if (!result.canceled) {
return result.filePaths;
}
return [];
}
// Open directory
async function openDirectory() {
const result = await dialog.showOpenDialog({
title: "Select Folder",
properties: ["openDirectory"]
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
}
// Save file
async function saveFile(defaultName = "untitled.txt") {
const result = await dialog.showSaveDialog({
title: "Save File",
defaultPath: path.join(paths.home, defaultName),
filters: [
{ name: "Text Files", extensions: ["txt"] },
{ name: "All Files", extensions: ["*"] }
]
});
if (!result.canceled) {
return result.filePath;
}
return null;
}
Message Boxes
// Confirmation dialog
async function confirmQuit() {
const result = await dialog.showMessageBox({
type: "question",
title: "Confirm Quit",
message: "Are you sure you want to quit?",
detail: "Unsaved changes will be lost.",
buttons: ["Quit", "Cancel"],
defaultId: 1,
cancelId: 1
});
return result.response === 0; // Returns true if "Quit" clicked
}
// Error dialog
async function showError(message: string, detail?: string) {
await dialog.showMessageBox({
type: "error",
title: "Error",
message,
detail,
buttons: ["OK"]
});
}
// Warning with options
async function showWarning() {
const result = await dialog.showMessageBox({
type: "warning",
title: "Warning",
message: "This action cannot be undone",
detail: "Are you sure you want to continue?",
buttons: ["Continue", "Cancel", "Learn More"],
defaultId: 1,
cancelId: 1
});
if (result.response === 0) {
// Continue
} else if (result.response === 2) {
shell.openExternal("https://docs.myapp.com/warning");
}
}
// Info dialog
async function showInfo(message: string) {
await dialog.showMessageBox({
type: "info",
title: "Information",
message,
buttons: ["OK"]
});
}
Keyboard Shortcuts
Global Shortcuts
import { globalShortcut } from "electrobun/bun";
// Register global shortcut
globalShortcut.register("CommandOrControl+Shift+Space", () => {
mainWindow.show();
mainWindow.focus();
});
// Register multiple shortcuts
const shortcuts = [
{ key: "CommandOrControl+1", action: () => switchToTab(0) },
{ key: "CommandOrControl+2", action: () => switchToTab(1) },
{ key: "CommandOrControl+3", action: () => switchToTab(2) },
];
shortcuts.forEach(({ key, action }) => {
globalShortcut.register(key, action);
});
// Unregister on quit
app.on("will-quit", () => {
globalShortcut.unregisterAll();
});
In-Window Shortcuts
// Define in menu (automatically registered)
{
label: "New Window",
accelerator: "CmdOrCtrl+N",
action: () => createWindow()
}
// Custom accelerators in webview
document.addEventListener("keydown", (e) => {
// Cmd/Ctrl + K
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
openCommandPalette();
}
// Cmd/Ctrl + P
if ((e.metaKey || e.ctrlKey) && e.key === "p") {
e.preventDefault();
openFileFinder();
}
});
Notifications
System Notifications
import { Notification } from "electrobun/bun";
function showNotification(title: string, body: string) {
new Notification({
title,
body,
icon: "assets://notification-icon.png",
sound: true
}).show();
}
// With actions (on supported platforms)
const notification = new Notification({
title: "New Message",
body: "You have a new message from Alice",
actions: [
{ type: "button", text: "Reply" },
{ type: "button", text: "Dismiss" }
]
});
notification.on("action", (index) => {
if (index === 0) {
openReplyWindow();
}
});
notification.show();
Resources
For more on Electrobun:
- Core skill:
electrobun- Basic UI setup - Window management:
electrobun-window-management- Window coordination - RPC patterns:
electrobun-rpc-patterns- UI state sync
Weekly Installs
29
Repository
rajavijayach/el…n-skillsGitHub Stars
3
First Seen
Feb 21, 2026
Security Audits
Installed on
opencode28
codex28
gemini-cli27
github-copilot27
amp27
kimi-cli27