electron-desktop
travisjneuman/.claude/skills/electron-desktop/SKILL.md
Desktop application development with Electron for Windows, macOS, and Linux. Use when building cross-platform desktop apps, implementing native OS features, or packaging web apps for desktop.
Skill99 starsChanged 7 months ago
- Reads credentials
What's in it
- Electron Desktop Development
- Platforms Supported
- Project Structure
- Main Process
- Entry Point
- Preload Script (Security Bridge)
- IPC Handlers
- Renderer Process (React)
- Using Exposed APIs
- Custom Title Bar CSS
- Native Features
- System Tray
- Native Menus
- Notifications
- Auto Updates
- Building & Distribution
- electron-builder Configuration
- Build Commands
- Security Best Practices
- DO:
- DON'T:
- Alternatives to Consider
---
name: electron-desktop
description: Desktop application development with Electron for Windows, macOS, and Linux. Use when building cross-platform desktop apps, implementing native OS features, or packaging web apps for desktop.
---
# Electron Desktop Development
Build cross-platform desktop applications using web technologies.
## Platforms Supported
| Platform | Architecture | Notes |
| -------- | -------------------------- | ------------------ |
| Windows | x64, arm64, ia32 | Windows 10+ |
| macOS | x64, arm64 (Apple Silicon) | macOS 10.15+ |
| Linux | x64, arm64, armv7l | Most distributions |
---
## Project Structure
```
my-app/
├── src/
│ ├── main/
│ │ ├── main.ts # Main process
│ │ ├── preload.ts # Preload scripts
│ │ └── ipc.ts # IPC handlers
│ ├── renderer/
│ │ ├── index.html
│ │ ├── App.tsx
│ │ └── components/
│ └── shared/
│ └── types.ts
├── resources/
│ ├── icon.icns # macOS
│ ├── icon.ico # Windows
│ └── icon.png # Linux
├── electron-builder.yml
├── package.json
└── forge.config.ts
```
---
## Main Process
### Entry Point
```typescript
// src/main/main.ts
import { app, BrowserWindow, ipcMain } from "electron";
import path from "path";
let mainWindow: BrowserWindow | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
titleBarStyle: "hiddenInset", // macOS
frame: process.platform === "darwin", // Windows/Linux custom frame
show: false, // Show when ready
});
// Load the app
if (process.env.NODE_ENV === "development") {
mainWindow.loadURL("http://localhost:5173");
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
}
// Show when ready to prevent flash
mainWindow.once("ready-to-show", () => {
mainWindow?.show();
});
mainWindow.on("closed", () => {
mainWindow = null;
});
}
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
```
### Preload Script (Security Bridge)
```typescript
// src/main/preload.ts
import { contextBridge, ipcRenderer } from "electron";
// Expose safe APIs to renderer
contextBridge.exposeInMainWorld("electronAPI", {
// File operations
openFile: () => ipcRenderer.invoke("dialog:openFile"),
saveFile: (content: string) => ipcRenderer.invoke("dialog:saveFile", content),
// App info
getVersion: () => ipcRenderer.invoke("app:getVersion"),
// Window controls
minimize: () => ipcRenderer.send("window:minimize"),
maximize: () => ipcRenderer.send("window:maximize"),
close: () => ipcRenderer.send("window:close"),
// Two-way communication
onUpdateAvailable: (callback: () => void) => {
ipcRenderer.on("update:available", callback);
return () => ipcRenderer.removeListener("update:available", callback);
},
});
// Type definitions for renderer
declare global {
interface Window {
electronAPI: {
openFile: () => Promise<string | null>;
saveFile: (content: string) => Promise<boolean>;
getVersion: () => Promise<string>;
minimize: () => void;
maximize: () => void;
close: () => void;
onUpdateAvailable: (callback: () => void) => () => void;
};
}
}
```
### IPC Handlers
```typescript
// src/main/ipc.ts
import { ipcMain, dialog, BrowserWindow } from "electron";
import fs from "fs/promises";
export function setupIPC() {
// File dialogs
ipcMain.handle("dialog:openFile", async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [
{ name: "Text Files", extensions: ["txt", "md"] },
{ name: "All Files", extensions: ["*"] },
],
});
if (canceled || filePaths.length === 0) return null;
return fs.readFile(filePaths[0], "utf-8");
});
ipcMain.handle("dialog:saveFile", async (_, content: string) => {
const { canceled, filePath } = await dialog.showSaveDialog({
filters: [{ name: "Text Files", extensions: ["txt"] }],
});
if (canceled || !filePath) return false;
await fs.writeFile(filePath, content);
return true;
});
// Window controls
ipcMain.on("window:minimize", (event) => {
BrowserWindow.fromWebContents(event.sender)?.minimize();
});
ipcMain.on("window:maximize", (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win?.isMaximized()) {
win.unmaximize();
} else {
win?.maximize();
}
});
ipcMain.on("window:close", (event) => {
BrowserWindow.fromWebContents(event.sender)?.close();
});
}
```
---
## Renderer Process (React)
### Using Exposed APIs
```tsx
// src/renderer/App.tsx
import { useState, useEffect } from "react";
function App() {
const [version, setVersion] = useState("");
const [content, setContent] = useState("");
useEffect(() => {
window.electronAPI.getVersion().then(setVersion);
const unsubscribe = window.electronAPI.onUpdateAvailable(() => {
alert("Update available!");
});
return unsubscribe;
}, []);
const handleOpen = async () => {
const fileContent = await window.electronAPI.openFile();
if (fileContent) {
setContent(fileContent);
}
};
const handleSave = async () => {
await window.electronAPI.saveFile(content);
};
return (
<div className="app">
<header className="titlebar">
<span>My App v{version}</span>
<div className="window-controls">
<button onClick={window.electronAPI.minimize}>−</button>
<button onClick={window.electronAPI.maximize}>□</button>
<button onClick={window.electronAPI.close}>×</button>
</div>
</header>
<main>
<button onClick={handleOpen}>Open File</button>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button onClick={handleSave}>Save File</button>
</main>
</div>
);
}
```
### Custom Title Bar CSS
```css
.titlebar {
-webkit-app-region: drag; /* Make draggable */
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
background: #1e1e1e;
color: white;
}
.window-controls {
-webkit-app-region: no-drag; /* Buttons clickable */
display: flex;
gap: 8px;
}
.window-controls button {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: white;
cursor: pointer;
}
.window-controls button:hover {
background: rgba(255, 255, 255, 0.1);
}
```
---
## Native Features
### System Tray
```typescript
import { Tray, Menu, nativeImage } from "electron";
let tray: Tray | null = null;
function createTray() {
const icon = nativeImage.createFromPath("resources/tray-icon.png");
tray = new Tray(icon.resize({ width: 16, height: 16 }));
const contextMenu = Menu.buildFromTemplate([
{ label: "Show App", click: () => mainWindow?.show() },
{ type: "separator" },
{ label: "Quit", click: () => app.quit() },
]);
tray.setToolTip("My App");
tray.setContextMenu(contextMenu);
tray.on("click", () => {
mainWindow?.show();
});
}
```
### Native Menus
```typescript
import { Menu } from "electron";
const template: Electron.MenuItemConstructorOptions[] = [
{
label: "File",
submenu: [
{
label: "Open",
accelerator: "CmdOrCtrl+O",
click: () => {
/* handle */
},
},
{
label: "Save",
accelerator: "CmdOrCtrl+S",
click: () => {
/* handle */
},
},
{ type: "separator" },
{ role: "quit" },
],
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
],
},
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
```
### Notifications
```typescript
import { Notification } from "electron";
new Notification({
title: "Update Available",
body: "A new version is ready to install.",
icon: "resources/icon.png",
}).show();
```
### Auto Updates
```typescript
import { autoUpdater } from "electron-updater";
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on("update-available", () => {
mainWindow?.webContents.send("update:available");
});
autoUpdater.on("update-downloaded", () => {
autoUpdater.quitAndInstall();
});
```
---
## Building & Distribution
### electron-builder Configuration
```yaml
# electron-builder.yml
appId: com.mycompany.myapp
productName: My App
copyright: Copyright © 2025
directories:
output: dist
buildResources: resources
files:
- dist/**/*
- package.json
mac:
category: public.app-category.developer-tools
target:
- target: dmg
arch: [x64, arm64]
- target: zip
arch: [x64, arm64]
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
notarize: true
win:
target:
- target: nsis
arch: [x64]
sign: true
linux:
target:
- target: AppImage
- target: deb
- target: rpm
category: Development
publish:
provider: github
releaseType: release
```
### Build Commands
```bash
# Development
npm run dev
# Build for current platform
npm run build
# Build for all platforms
npm run build:all
# Build for specific platform
npm run build:mac
npm run build:win
npm run build:linux
```
---
## Security Best Practices
### DO:
- Always use `contextIsolation: true`
- Use preload scripts for IPC
- Validate all IPC inputs
- Enable `sandbox: true`
- Sign and notarize for distribution
### DON'T:
- Enable `nodeIntegration`
- Use `remote` module
- Load untrusted content
- Expose full Node.js APIs
- Skip code signing
---
## Alternatives to Consider
| Framework | Best For |
| -------------- | ------------------------------ |
| **Tauri** | Smaller bundle, Rust backend |
| **Neutralino** | Lightweight, system webview |
| **Electron** | Full Node.js, mature ecosystem |
More agent context in travisjneuman/.claude
127 other files this repository gives its agents, the first 60 shown.
CLAUDE.md
Skill
- accessibility-a11yskills/accessibility-a11y/SKILL.md
- agent-teamsskills/agent-teams/SKILL.md
- ai-ml-developmentskills/ai-ml-development/SKILL.md
- ai-policy-generatorskills/ai-policy-generator/SKILL.md
- android-developmentskills/android-development/SKILL.md
- api-designskills/api-design/SKILL.md
- application-securityskills/application-security/SKILL.md
- ar-vr-xrskills/ar-vr-xr/SKILL.md
- audio-productionskills/audio-production/SKILL.md
- authentication-patternsskills/authentication-patterns/SKILL.md
- auto-claudeskills/auto-claude/SKILL.md
- battle-card-builderskills/battle-card-builder/SKILL.md
- blockchain-web3skills/blockchain-web3/SKILL.md
- brand-identityskills/brand-identity/SKILL.md
- business-strategyskills/business-strategy/SKILL.md
- career-path-plannerskills/career-path-planner/SKILL.md
- case-interview-practiceskills/case-interview-practice/SKILL.md
- codebase-documenterskills/codebase-documenter/SKILL.md
- compliance-engineeringskills/compliance-engineering/SKILL.md
- content-repurposerskills/content-repurposer/SKILL.md
- contract-redlinerskills/contract-redliner/SKILL.md
- core-workflowskills/core-workflow/SKILL.md
- course-material-creatorskills/course-material-creator/SKILL.md
- customer-persona-builderskills/customer-persona-builder/SKILL.md
- customer-successskills/customer-success/SKILL.md
- database-expertskills/database-expert/SKILL.md
- data-engineeringskills/data-engineering/SKILL.md
- data-scienceskills/data-science/SKILL.md
- debate-practice-coachskills/debate-practice-coach/SKILL.md
- debug-systematicskills/debug-systematic/SKILL.md
- devex-sdk-designskills/devex-sdk-design/SKILL.md
- devops-cloudskills/devops-cloud/SKILL.md
- docxskills/document-skills/docx/SKILL.md
- pdfskills/document-skills/pdf/SKILL.md
- pptxskills/document-skills/pptx/SKILL.md
- document-skillsskills/document-skills/SKILL.md
- xlsxskills/document-skills/xlsx/SKILL.md
- edge-computingskills/edge-computing/SKILL.md
- email-systemsskills/email-systems/SKILL.md
- embedded-iotskills/embedded-iot/SKILL.md
- event-driven-architectureskills/event-driven-architecture/SKILL.md
- event-plannerskills/event-planner/SKILL.md
- financeskills/finance/SKILL.md
- financial-scenario-plannerskills/financial-scenario-planner/SKILL.md
- flutter-developmentskills/flutter-development/SKILL.md
- frontend-enhancerskills/frontend-enhancer/SKILL.md
- fundraising-analyzerskills/fundraising-analyzer/SKILL.md
- game-developmentskills/game-development/SKILL.md
- generic-code-reviewerskills/generic-code-reviewer/SKILL.md
- generic-design-systemskills/generic-design-system/SKILL.md
- generic-feature-developerskills/generic-feature-developer/SKILL.md
- generic-fullstack-code-reviewerskills/generic-fullstack-code-reviewer/SKILL.md
- generic-fullstack-design-systemskills/generic-fullstack-design-system/SKILL.md
- generic-fullstack-feature-developerskills/generic-fullstack-feature-developer/SKILL.md
- generic-fullstack-ux-designerskills/generic-fullstack-ux-designer/SKILL.md
- generic-react-code-reviewerskills/generic-react-code-reviewer/SKILL.md
- generic-react-design-systemskills/generic-react-design-system/SKILL.md
- generic-react-feature-developerskills/generic-react-feature-developer/SKILL.md
- generic-react-ux-designerskills/generic-react-ux-designer/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

