Electron Native Feature Integration
Electron is powerful because it can not only write frontend code, but also access native system features, such as menus, dialogs, tray, notifications, and global shortcuts.
In this section, we'll look at how to call these features through main process modules.
Menu system
Electron providesMenuThe module is used to create menu bars, context menus, and system tray menus.
Application Menu (Menu)
The application menu is a menu bar displayed at the top of the window (macOS) or within the application window.
Example
const template = [
{
label: 'File',
submenu: [
{ label: 'New File', accelerator: 'CmdOrCtrl+N', click: () => console.log('New File') },
{ label: 'Open...', accelerator: 'CmdOrCtrl+O' },
{ type: 'separator' },
{ label: 'Exit', role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
Description:
label: Menu title.submenu: Submenu array.acceleratorKeyboard shortcuts (common notation for macOS, WindowsCmdOrCtrl+X)。role: System-predefined behaviors, such asquit、copy、paste。click: Click event callback function.
Context Menu
Context menus are usually triggered in the renderer process:
Example
const { ipcMain, Menu } = require('electron')
ipcMain.on('show-context-menu', (event) => {
const menu = Menu.buildFromTemplate([
{ label: 'Refresh', click: () => event.sender.reload() },
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' }
])
menu.popup()
})
Example
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
window.electronAPI.showContextMenu()
})
System tray menu
Display an icon and menu in the system notification area.
Example
let tray = null
function createTray() {
const icon = nativeImage.createFromPath('icon.png')
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: 'Open main window', click: () => mainWindow.show() },
{ label: 'Exit', click: () => app.quit() }
])
tray.setToolTip('My Electron Application')
tray.setContextMenu(contextMenu)
}
Menu shortcuts
ThroughacceleratorDefinition. For example:
{ label: '保存', accelerator: 'CmdOrCtrl+S', click: saveFile }
CmdOrCtrl: Automatically adapts to macOS/Windows.Alt+F4: Windows closes the window.Cmd+Q: macOS quits the application.
System dialogs
dialogThe module provides native system dialogs for files, messages, errors, etc.
File/Folder selectionconst { dialog } = require('electron')
async function openFileDialog() {
const result = await dialog.showOpenDialog({
title: '选择文件',
properties: ['openFile', 'multiSelections'],
filters: [{ name: '文本文件', extensions: ['txt', 'md'] }]
})
console.log(result.filePaths)
}
Save dialog
async function saveFileDialog() {
const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: '新建文档.txt'
})
console.log(result.filePath)
}
Message/Error dialog
dialog.showMessageBox({
type: 'info',
title: '提示',
message: '操作已完成!'
})
dialog.showErrorBox('错误', '文件保存失败,请重试。')
System tray
The tray (Tray) allows the application to interact through the system icon while running in the background.
Create tray icon
const { Tray } = require('electron')
const path = require('path')
let tray = new Tray(path.join(__dirname, 'trayIcon.png'))
Tray menu and tooltip
const contextMenu = Menu.buildFromTemplate([
{ label: '显示窗口', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
])
tray.setToolTip('Electron 应用运行中')
tray.setContextMenu(contextMenu)
Tray notification
tray.displayBalloon({
icon: path.join(__dirname, 'icon.png'),
title: '新消息',
content: '您有一条新通知'
})
Minimize to tray
Listen for close events to hide the window:
mainWindow.on('close', (event) => {
event.preventDefault()
mainWindow.hide()
})
Notifications
Electron'sNotificationThe class can display system notifications across platforms.
System notification sending
new Notification({
title: '任务完成',
body: '您的文件已下载完成'
}).show()
Notification interaction handling
const notif = new Notification({ title: '点击通知', body: '点击我触发事件' })
notif.on('click', () => {
console.log('用户点击了通知')
})
notif.show()
Cross-platform compatibility
- macOS, Windows, and Linux are all supported;
- macOS requires enabling "Notification Permission";
- Linux usually requires system support
libnotify。
Global shortcuts
ThroughglobalShortcutRegister system-level hotkey.
Register global shortcut
const { globalShortcut } = require('electron')
app.whenReady().then(() => {
globalShortcut.register('CmdOrCtrl+Shift+I', () => {
console.log('快捷键触发:打开调试工具')
mainWindow.webContents.openDevTools()
})
})
Shortcut conflict handling
Can be detected before registration:
if (globalShortcut.isRegistered('CmdOrCtrl+Shift+I')) {
console.log('快捷键已被占用')
}
Platform difference handling
Availability condition check:
const shortcut = process.platform === 'darwin' ? 'Cmd+Shift+T' : 'Ctrl+Shift+T'
globalShortcut.register(shortcut, () => console.log('快捷键触发'))
Summary
| Features | Module | Typical use |
|---|---|---|
| Menu | Menu |
Application menu, context menu, tray menu |
| dialog box | dialog |
Open files, save files, display alerts or errors |
| Tray | Tray |
Background running, system status display |
| Notification | Notification |
Notify users of event progress, system reminders |
| Global shortcuts | globalShortcut |
Register system-level shortcut operations |
Electron Native Features Demo Project
Project Structure
my-electron-native-demo/ ├── package.json ├── main.js # 主进程逻辑 ├── preload.js # 预加载脚本(安全通信) └── index.html # 主界面
package.json
Note: Do not write comments in JSON files (otherwise an EJSONPARSE error will be reported).
{
"name": "my-electron-native-demo",
"version": "1.0.0",
"description": "Electron 原生功能演示项目",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "latest"
}
}
Install dependencies:
npm install
Start the application:
npm start
main.js (main process)
Example
const path = require('path')
let mainWindow, tray
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
// Display after page load completes
mainWindow.once('ready-to-show', () => mainWindow.show())
// Listen for window close: minimize to tray
mainWindow.on('close', (event) => {
event.preventDefault()
mainWindow.hide()
})
}
function createMenu() {
const template = [
{
label: 'File',
submenu: [
{ label: 'Open File', click: openFile },
{ label: Save File, click: saveFile },
{ type: 'separator' },
{ label: 'Exit', click: () => app.quit() }
]
},
{
label: 'Tools',
submenu: [
{ label: Show Notification, click: showNotification },
{ label: 'About', click: showAbout }
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
function createTray() {
const icon = path.join(__dirname, 'icon.png') // Please prepare a 16x16 or 32x32 icon
tray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate([
{ label: Show Window, click: () => mainWindow.show() },
{ label: 'Exit', click: () => app.quit() }
])
tray.setToolTip('Electron native features demo')
tray.setContextMenu(contextMenu)
}
function registerShortcuts() {
globalShortcut.register('CmdOrCtrl+Shift+I', () => {
mainWindow.webContents.openDevTools()
})
}
async function openFile() {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text File', extensions: ['txt', 'md'] }]
})
if (!result.canceled && result.filePaths.length > 0) {
mainWindow.webContents.send('file-opened', result.filePaths[0])
}
}
async function saveFile() {
const result = await dialog.showSaveDialog({
title: Save File,
defaultPath: 'New Document.txt'
})
if (!result.canceled) {
dialog.showMessageBox({ type: 'info', message:`File will be saved to: ${result.filePath}` })
}
}
function showNotification() {
const notif = new Notification({
title: 'Electron notification',
body: 'This is a system notification!'
})
notif.on('click', () => {
dialog.showMessageBox({ message: 'You clicked the notification!' })
})
notif.show()
}
function showAbout() {
dialog.showMessageBox({
type: 'info',
title: 'About',
message: 'Electron native features demo project\n'Version: 1.0.0'
})
}
// Renderer process call
ipcMain.handle('show-dialog', async () => {
const result = await dialog.showMessageBox({
type: 'info',
title: 'From main process',
message: 'This is a dialog box displayed by the main process!'
})
return result.response
})
// Lifecycle
app.whenReady().then(() => {
createWindow()
createMenu()
createTray()
registerShortcuts()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
preload.js (secure bridge)
Example
contextBridge.exposeInMainWorld('electronAPI', {
showDialog: () => ipcRenderer.invoke('show-dialog')
})
index.html (frontend interface)
Example
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Electron native functionality demo</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 80px; }
button { padding: 10px 20px; margin: 10px; font-size: 16px; cursor: pointer; }
#filePath { color: #666; font-size: 14px; }
</style>
</head>
<body>
<h1>Electron native functionality demo</h1>
<p><button id="dialogBtn">Show dialog</button></p>
<p id="filePath"></p>
<script>
document.getElementById('dialogBtn').addEventListener('click', async () => {
await window.electronAPI.showDialog()
})
const filePathEl = document.getElementById('filePath')
window.electronAPI && window.electronAPI.fileOpened && window.electronAPI.fileOpened((path) => {
filePathEl.textContent = 'Opened file path: ' + path
})
</script>
</body>
</html>
Runnpm startAfter that, functions are as follows:
- The top menu bar contains the "File" and "Tools" menus.
- "File" → Open File / Save File brings up the system dialog.
- "Tools" → Show system notification, display the "About" window.
- An application icon appears in the tray area, and you can right-click to show a menu.
- Minimize to the tray after the window is closed.
- Register global shortcut
CmdOrCtrl+Shift+IOpen developer tools.