Desktop ui electron
Skill agents-inc/skills/dist/plugins/desktop-ui-electron/skills/desktop-ui-electron
Frameless windows, custom title bars, tray, menus, dock badges, vibrancy, kiosk mode, window state persistenceFrom its SKILL.md
npx -y skills add agents-inc/skills --skill desktop-ui-electronAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
17.2 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it
Electron Desktop UI Patterns
Quick Guide: Use
titleBarStyle: 'hidden'for custom title bars with native traffic lights on macOS, combined withtitleBarOverlayfor Windows/Linux window controls. Mark draggable regions withapp-region: dragin CSS and exclude interactive elements withapp-region: no-drag. Keep a module-level reference toTrayobjects (garbage collection silently destroys the icon). Usevibrancyfor macOS translucency effects andbackgroundMaterialfor Windows 11 Mica/Acrylic. Persist window bounds manually withgetBounds()/setBounds()on thecloseevent.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST keep a module-level reference to Tray objects -- garbage collection silently destroys the tray icon with no error)
(You MUST use app-region: no-drag on ALL interactive elements (buttons, inputs, links) inside a drag region -- draggable areas swallow all pointer events)
(You MUST add user-select: none to draggable title bar regions -- dragging conflicts with text selection)
(You MUST NOT use transparent: true with backgroundMaterial on Windows -- set backgroundColor: '#00000000' instead to allow the DWM material to show through)
</critical_requirements>
Auto-detection: titleBarStyle, titleBarOverlay, trafficLightPosition, frameless window, frame false, app-region drag, custom title bar, Tray, system tray, tray icon, Menu.buildFromTemplate, context menu, app.setBadgeCount, dock badge, splash screen, kiosk, alwaysOnTop, vibrancy, backgroundMaterial, mica, acrylic, transparent window, electron-window-state, window state persistence, getBounds, setBounds
When to use:
- Building custom title bars (frameless, overlay controls, macOS traffic light positioning)
- Creating system tray icons with context menus
- Building application menus and context menus
- Adding dock/taskbar badges for notifications
- Implementing splash screens or always-on-top windows
- Making windows transparent or applying vibrancy/material effects
- Persisting and restoring window position and size
- Entering kiosk mode for single-app displays
When NOT to use:
- Choosing a UI framework for the renderer content (not this skill's scope)
- Styling renderer page content (not this skill's scope)
- Configuring IPC or preload security (separate from window chrome customization)
- Packaging or distributing the application (separate from window chrome customization)
Key patterns covered:
- Frameless windows with
titleBarStyle: 'hidden'andtitleBarOverlay - Custom title bars with CSS
app-region: drag/no-drag - macOS traffic light positioning via
trafficLightPosition - Native application menus and context menus
- System tray icons with menus and click handlers
- Dock/taskbar badges (
app.setBadgeCount,app.dock.setBadge) - Splash screens and always-on-top windows
- Window state persistence (position, size, maximized)
- Transparent windows, vibrancy (macOS), backgroundMaterial (Windows 11)
- Kiosk mode
<philosophy>
Philosophy
Electron desktop UI customization operates at two levels: window chrome (title bar, frame, transparency, system tray) controlled via BrowserWindow constructor options and main process APIs, and in-window layout (drag regions, custom title bar HTML/CSS) controlled via the renderer. The main process owns window-level behavior; the renderer owns the visual presentation within the window.
Platform-aware design is essential. macOS has native traffic lights and vibrancy. Windows 11 has Mica/Acrylic materials and titleBarOverlay for window controls. Linux varies by desktop environment. Always test UI customizations on all target platforms -- what works on macOS may look wrong on Windows or be unsupported on Linux.
When to customize window chrome:
- App requires a branded header or sidebar navigation alongside window controls
- App needs to minimize visual chrome (media player, creative tool)
- App runs in kiosk/display mode (digital signage, POS terminal)
- App needs persistent system tray presence (background services, communication tools)
When NOT to customize:
- Standard document-based apps where native title bar is expected
- Apps where accessibility is the top priority (custom title bars can break screen readers)
- When native platform look-and-feel is more important than branding
<patterns>
Core Patterns
Pattern 1: Frameless Windows and Custom Title Bars
Use titleBarStyle: 'hidden' to remove the native title bar while keeping macOS traffic lights. On Windows/Linux, add titleBarOverlay to get native window control buttons overlaid on your content.
const TITLE_BAR_OVERLAY_HEIGHT = 40;
const mainWindow = new BrowserWindow({
titleBarStyle: "hidden",
// Windows/Linux: overlay native controls on custom title bar
titleBarOverlay: {
color: "#2f3241",
symbolColor: "#74b1be",
height: TITLE_BAR_OVERLAY_HEIGHT,
},
// macOS: position traffic lights within custom title bar
trafficLightPosition: { x: 16, y: 12 },
});
Key point: titleBarStyle: 'hidden' hides the title text and title bar area but keeps macOS traffic lights visible. titleBarOverlay creates a Windows Controls Overlay (WCO) with native minimize/maximize/close buttons on Windows and Linux. See examples/core.md for the full CSS title bar implementation.
Pattern 2: CSS Drag Regions
Mark custom title bar areas as draggable with app-region: drag. All interactive elements (buttons, inputs) inside a drag region MUST be marked app-region: no-drag or they will be unclickable.
.title-bar {
app-region: drag;
user-select: none;
height: 40px;
}
.title-bar button,
.title-bar input {
app-region: no-drag;
}
Key point: Draggable areas swallow ALL pointer events -- no clicks, no hover, no cursor changes. Only rectangular regions are supported. Never use custom context menus on drag regions (right-click triggers the system window menu on some platforms). See examples/core.md.
Pattern 3: Application Menus and Context Menus
Use Menu.buildFromTemplate() for both application menus and right-click context menus. On macOS, the first menu item is always the app name menu.
const { Menu, app } = require("electron/main");
const template = [
...(process.platform === "darwin"
? [{ label: app.name, submenu: [{ role: "about" }, { role: "quit" }] }]
: []),
{
label: "File",
submenu: [
{ label: "Open", accelerator: "CmdOrCtrl+O", click: () => { /* ... */ } },
process.platform === "darwin" ? { role: "close" } : { role: "quit" },
],
},
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
Key point: Use built-in role values (undo, copy, paste, quit, about, etc.) for standard actions -- Electron handles platform-specific labels and shortcuts automatically. See examples/core.md for context menus.
Pattern 4: System Tray
Create a persistent system tray icon with a context menu. You MUST keep a module-level reference to the Tray object or it will be garbage collected and the icon disappears silently.
const { Tray, Menu, nativeImage } = require("electron/main");
let tray = null; // MUST keep reference -- GC destroys the icon silently
function createTray(mainWindow) {
const icon = nativeImage.createFromPath(path.join(__dirname, "assets", "tray-icon.png"));
if (process.platform === "darwin") icon.setTemplateImage(true);
tray = new Tray(icon);
tray.setToolTip(app.name);
tray.setContextMenu(Menu.buildFromTemplate([
{ label: "Show", click: () => { mainWindow.show(); mainWindow.focus(); } },
{ type: "separator" },
{ label: "Quit", click: () => app.quit() },
]));
}
Key point: macOS tray icons should be 16x16 template images (monochrome) -- call setTemplateImage(true) so the OS renders them correctly in dark/light mode. On Windows/Linux, handle tray.on('click') for left-click behavior. See examples/core.md for balloon notifications.
Pattern 5: Dock and Taskbar Badges
Use app.setBadgeCount() (macOS, Linux/Unity) for numeric badges or app.dock.setBadge() (macOS only) for text badges.
// Numeric badge (macOS + Linux/Unity)
app.setBadgeCount(5); // Shows "5" on dock/taskbar icon
app.setBadgeCount(0); // Hides the badge
// Text badge (macOS only)
app.dock.setBadge("!"); // Shows "!" on dock icon
app.dock.setBadge(""); // Clears the badge
Key point: setBadgeCount returns boolean (false if platform doesn't support it). On Windows, use a third-party overlay solution -- native badge API is not available. See examples/core.md.
Pattern 6: Window State Persistence
Save and restore window position, size, and maximized state across app restarts using getBounds()/setBounds() and a local JSON file.
const WINDOW_STATE_FILE = "window-state.json";
function loadWindowState() {
try {
const data = fs.readFileSync(path.join(app.getPath("userData"), WINDOW_STATE_FILE), "utf-8");
return JSON.parse(data);
} catch {
return null;
}
}
function saveWindowState(win) {
const bounds = win.getBounds();
const data = { ...bounds, isMaximized: win.isMaximized() };
fs.writeFileSync(path.join(app.getPath("userData"), WINDOW_STATE_FILE), JSON.stringify(data));
}
Key point: Save state on the close event (not closed -- the window is already destroyed). Validate saved bounds against current display geometry to avoid restoring off-screen. See examples/window-state.md for the complete implementation with display validation.
Pattern 7: Transparent Windows and Visual Effects
Use transparent: true for fully transparent windows (overlays, widgets). Use vibrancy (macOS) or backgroundMaterial (Windows 11) for system-level translucency effects.
// macOS vibrancy
const win = new BrowserWindow({
vibrancy: "sidebar",
visualEffectState: "active",
backgroundColor: "#00000000",
});
// Windows 11 Mica/Acrylic -- do NOT use transparent: true
const win = new BrowserWindow({
backgroundMaterial: "mica",
backgroundColor: "#00000000",
});
Key point: For backgroundMaterial on Windows, set backgroundColor: '#00000000' instead of transparent: true -- the latter enables layered window mode which breaks material rendering. vibrancy accepts 15+ values; sidebar and under-window are the most common. See examples/core.md.
Pattern 8: Splash Screens and Always-On-Top Windows
Create a splash screen as a frameless, always-on-top window that closes after the main window loads.
const splash = new BrowserWindow({
width: 400,
height: 300,
frame: false,
alwaysOnTop: true,
transparent: true,
skipTaskbar: true,
resizable: false,
});
splash.loadFile("splash.html");
mainWindow.once("ready-to-show", () => {
splash.destroy();
mainWindow.show();
});
Key point: Use skipTaskbar: true so the splash doesn't appear in the taskbar. Show the main window only after ready-to-show fires (content is rendered). See examples/core.md for kiosk mode.
Pattern 9: Kiosk Mode
Kiosk mode makes the window fullscreen with no way for the user to exit via standard OS controls.
const kiosk = new BrowserWindow({
kiosk: true,
// alwaysOnTop: true, // optional: stay above other apps
});
// Exit kiosk programmatically
kiosk.setKiosk(false);
Key point: Kiosk mode disables window controls, Alt+F4 on Windows, and Cmd+Q on macOS. Provide an in-app exit mechanism (admin button, keyboard shortcut via IPC) or the user cannot close the app.
</patterns><decision_framework>
Decision Framework
Window Chrome Strategy
Need custom branding in the title bar?
+-- YES --> titleBarStyle: 'hidden' + titleBarOverlay (Windows/Linux)
| + custom HTML/CSS title bar with app-region: drag
+-- NO --> Keep default frame: true
Need fully frameless (no controls at all)?
+-- YES --> frame: false + implement ALL window controls in HTML
+-- NO --> Use titleBarStyle: 'hidden' (keeps native controls)
Need transparency?
+-- Fully transparent (overlay widget)? --> transparent: true + frame: false
+-- macOS frosted glass? --> vibrancy: 'sidebar' (or other type)
+-- Windows 11 material? --> backgroundMaterial: 'mica' | 'acrylic'
Title Bar Options by Platform
| Option | macOS | Windows | Linux |
|---|---|---|---|
titleBarStyle: 'hidden' | Hides title, keeps traffic lights | Hides title bar entirely | Hides title bar entirely |
titleBarStyle: 'hiddenInset' | Traffic lights inset further | N/A (same as hidden) | N/A (same as hidden) |
titleBarOverlay | Not needed (traffic lights stay) | Adds native min/max/close buttons | Adds native min/max/close buttons |
trafficLightPosition | Custom traffic light position | N/A | N/A |
frame: false | No chrome at all | No chrome at all | No chrome at all |
System Tray vs Dock Badge
Need background presence after window close?
+-- YES --> System tray (Tray) + tray.setContextMenu()
+-- NO --> Just show/hide the main window
Need notification count on app icon?
+-- macOS? --> app.setBadgeCount() or app.dock.setBadge()
+-- Linux/Unity? --> app.setBadgeCount()
+-- Windows? --> Third-party taskbar overlay
</decision_framework>
Detailed resources:
- examples/core.md - Custom title bars, drag regions, tray, menus, badges, transparent windows, splash screens, kiosk mode
- examples/window-state.md - Window state persistence with display validation
- reference.md - Quick-reference tables, vibrancy values, platform support matrix
<red_flags>
RED FLAGS
High Priority Issues:
- Losing the
Trayreference (no module-level variable) -- tray icon disappears silently when garbage collected - Missing
app-region: no-dragon buttons/inputs inside a drag region -- they become unclickable with no visible indication - Using
transparent: truewithbackgroundMaterialon Windows -- breaks material rendering; usebackgroundColor: '#00000000'instead - Using
frame: falsewithout implementing window controls -- users cannot minimize, maximize, or close the window - Missing
user-select: noneon drag regions -- dragging accidentally selects title bar text
Medium Priority Issues:
- Not providing a macOS-specific first menu item (app name menu) -- menu bar looks wrong on macOS
- Missing platform checks for macOS-only APIs (
vibrancy,trafficLightPosition,dock.setBadge) - Not using template images for macOS tray icons -- icon does not adapt to dark/light mode
- Saving window state on
closedinstead ofclose-- window is already destroyed,getBounds()fails - Not validating restored window bounds against current displays -- window appears off-screen after display changes
Gotchas & Edge Cases:
titleBarOverlayis not needed on macOS -- traffic lights stay visible automatically withtitleBarStyle: 'hidden'titleBarStyle: 'hiddenInset'andcustomButtonsOnHoverare macOS-only -- they behave like'hidden'on other platforms- Tray
clickevent does not fire on macOS when a context menu is set -- macOS always shows the context menu on any click app.setBadgeCount()requires notification permissions on macOS and a.desktopfile on LinuxbackgroundMaterialrequires Windows 11 22H2 or later -- it silently does nothing on older Windows versionsvibrancyvalues likeappearance-basedare deprecated -- prefersidebar,under-window,content- Kiosk mode disables standard OS exit shortcuts (Alt+F4, Cmd+Q) -- provide an in-app exit mechanism
- Right-clicking a drag region triggers the system window menu on some platforms -- never use custom context menus on drag regions
setAlwaysOnTop(true, 'screen-saver')places the window above macOS fullscreen apps but is discouraged by Apple for non-screen-saver use- On Linux, tray support depends on the desktop environment -- GNOME requires an extension; KDE and XFCE support it natively
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST keep a module-level reference to Tray objects -- garbage collection silently destroys the tray icon with no error)
(You MUST use app-region: no-drag on ALL interactive elements (buttons, inputs, links) inside a drag region -- draggable areas swallow all pointer events)
(You MUST add user-select: none to draggable title bar regions -- dragging conflicts with text selection)
(You MUST NOT use transparent: true with backgroundMaterial on Windows -- set backgroundColor: '#00000000' instead to allow the DWM material to show through)
Failure to follow these rules will cause invisible tray icons, unclickable buttons, broken transparency, and poor cross-platform behavior.
</critical_reminders>
What ships with it: 3 files
27.8 KB alongside SKILL.md
examples/
- core.md16.2 KB
- window-state.md6.3 KB
- reference.md5.3 KB