electron-desktop

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.

31 stars

Best use case

electron-desktop is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using electron-desktop should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/electron-desktop/SKILL.md --create-dirs "https://raw.githubusercontent.com/travisjneuman/.claude/main/skills/electron-desktop/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/electron-desktop/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How electron-desktop Compares

Feature / Agentelectron-desktopStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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 |

Related Skills

tauri-desktop

31
from travisjneuman/.claude

Tauri 2.0 project setup, Rust backend + web frontend, plugin system, IPC commands, security model, auto-update, and mobile support. Use when building lightweight cross-platform desktop or mobile apps with Tauri.

example-skill

31
from travisjneuman/.claude

Example skill - replace with your skill's description and activation keywords

websockets-realtime

31
from travisjneuman/.claude

Real-time communication with WebSockets, Server-Sent Events, and related technologies. Use when building chat, live updates, collaborative features, or any real-time functionality.

video-production

31
from travisjneuman/.claude

Professional video production from planning to delivery. Use when creating video content, editing workflows, motion graphics, or optimizing video for different platforms.

ui-research

31
from travisjneuman/.claude

Research-first UI/UX design workflow. Use BEFORE any frontend visual work to research modern patterns, gather inspiration from real products, and avoid generic AI-generated looks. Mandatory prerequisite for quality UI work.

ui-animation

31
from travisjneuman/.claude

Motion design and animation for user interfaces. Use when creating micro-interactions, page transitions, loading states, or any UI animation across web and mobile platforms.

travel-planner

31
from travisjneuman/.claude

Travel destination research and daily itinerary creation with logistics planning, budget tracking, and experience optimization. Use when planning trips, creating travel itineraries, comparing destinations, or organizing travel logistics.

test-specialist

31
from travisjneuman/.claude

This skill should be used when writing test cases, fixing bugs, analyzing code for potential issues, or improving test coverage for JavaScript/TypeScript applications. Use this for unit tests, integration tests, end-to-end tests, debugging runtime errors, logic bugs, performance issues, security vulnerabilities, and systematic code analysis.

tech-debt-analyzer

31
from travisjneuman/.claude

This skill should be used when analyzing technical debt in a codebase, documenting code quality issues, creating technical debt registers, or assessing code maintainability. Use this for identifying code smells, architectural issues, dependency problems, missing documentation, security vulnerabilities, and creating comprehensive technical debt documentation.

tdd-workflow

31
from travisjneuman/.claude

Test-Driven Development workflow enforcement with RED-GREEN-REFACTOR cycle. Use when implementing features test-first or improving test coverage.

svelte-development

31
from travisjneuman/.claude

Svelte 5 development with runes ($state, $derived, $effect), SvelteKit full-stack framework, and modern reactive patterns. Use when building Svelte applications, implementing fine-grained reactivity, or working with SvelteKit routing and server functions.

sustainability-esg

31
from travisjneuman/.claude

Sustainability and ESG expertise for ESG strategy, carbon footprint management, sustainable supply chains, circular economy, renewable energy, climate risk, and ESG reporting (GRI, SASB, TCFD). Use when building sustainability programs, measuring carbon impact, or creating ESG reports.