Crear un menú en Electron.js es una de esas tareas que parece sencilla, como lo es, Habilitar la integración con Node… hasta que empiezas a añadir submenús, roles, atajos de teclado, canales IPC y, para rematar, compatibilidad multiplataforma. En mi caso, la forma en que trabajo los menús ha evolucionado mucho: desde simples templates hasta menús completos para proyectos reales, como un editor con funciones de abrir/guardar, negritas, cursivas y títulos H1/H2 enviados al renderer.
En esta guía te voy a contar cómo crear un menú en Electron.js paso a paso, con ejemplos listos para copiar/pegar y, sobre todo, con casos reales que uso en mis proyectos y en mis cursos.
Qué es un menú en Electron.js y cómo funciona el template
Un menú en Electron no es más que un array de objetos con una estructura predefinida. Así de simple. Cada objeto representa un elemento del menú y puede incluir propiedades como label, submenu, click, role, type o accelerator.
Es decir, cuando hablo de “template”, hablo literalmente de algo como:
const { Menu } = require('electron')
const template = [
{
label: 'About us',
submenu: [
{ label: 'About the app', click() { /* ... */ } }
]
}
];Como puedes ver, la estructura de los menús no es más que un array de objetos con una estructura predefinida; podemos definir las opciones en el menú de dos tipos, personalizadas y con roles predefinidos como presentaremos en los siguientes apartados.
Cada menú está acompañado de un submenú en el cual indicamos los ítems del menú, que pueden ser personalizados o de roles predefinidos que veremos más adelante; en definitiva y como puedes ver en la estructura anterior, un menú está compuesto de un array, por lo tanto, podemos colocar tantos ítems en nuestro menú como necesitemos; a su vez, la propiedad de submenus es un array asi que, puedes indicar tantos items como desees.
Estructura básica: array de objetos y submenús
El patrón siempre es:
- Nivel 1 → Grupo principal (File, Edit, View, etc.)
- Nivel 2 → Submenús (Guardar, Abrir, Copiar…)
- Nivel 3 → Acciones o roles
Este modelo es idéntico en todas las plataformas, aunque macOS tiene algunas particularidades (sobre todo en el menú principal).
Diferencias entre opciones personalizadas y roles
Veamos los dos tipos de opciones que tenemos
Opciones personalizadas
La idea principal de los menús es que, podamos incluir opciones personalizadas para poder usar a lo largo de la aplicación; por ende, se usan como un evento de tipo click en JavaScript asociado a un botón (por ejemplo) pero, en este caso, en vez de un botón, es una opción en un menú:
const { Menu, shell } = require('electron')
const template = [
{
role: 'About Electron',
submenu: [
{
label: "About the app",
click() {
shell.openExternal("https://www.electronjs.org")
}
},
]
},
]Con el shell, podemos abrir una página web en un enlace, como si se tratase de un enlace de navegación.
Su uso es muy sencillo, y son las que empleamos cuando queremos usar algún método personalizado.
Son acciones que defines tú. Por ejemplo, abrir una URL, enviar un evento al renderer o activar un modo oscuro.
Roles predefinidos
Con los roles ya contamos con funciones específicas que podemos reutilizar fácilmente:
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ role: 'togglefullscreen' }Los cuales permiten:
- reload, Recargar la página.
- forceReload, Forzar la recarga de la página (vienen siendo un host reloading).
- toggleDevTools, Activar la consola de desarrolladores del navegador.
- resetZoom, Restablecer el zoom.
- zoomIn, Zoom In, aumentar el zoom.
- zoomOut, Zoom Out, Reducir el zoom.
- togglefullscreen, Habilita el modo full screen de la aplicación.
También tenemos el separador para organizar los menús:
{
type:'separator'
},Son funciones nativas que Electron ya trae listas: reload, zoomIn, toggleDevTools, etc.
En un menú serio uso ambas. Por ejemplo, roles para cosas de sistema, y acciones personalizadas para abrir archivos o aplicar estilos.
Volviendo a la clasificación o opciones, tenemos dos:
Crear un menú personalizado en Electron (paso a paso)
Creamos un menú sencillo como:
menu.js
const { app, ipcMain, Menu, shell, BrowserWindow, globalShortcut } = require('electron')
const { open_file, save_file } = require("./editor-options")
const template = [
{
label: 'About us',
click() {
shell.openExternal("https://www.electronjs.org")
}
},
{
label: 'File',
submenu: [
{
label: "Save",
accelerator: 'CommandOrControl+Shift+S',
click() {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editorchannel', 'file-save')
}
},
{
label: "Open",
accelerator: 'CommandOrControl+Shift+O',
click() {
const win = BrowserWindow.getFocusedWindow()
open_file(win)
}
},
]
},
{
label: 'Style and format',
submenu: [
{
label: 'Bold',
click() {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editorchannel', 'style-bold')
console.log("Negritas")
}
},
{
label: 'Italic',
click() {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editor-channel', 'style-italic')
}
},
{
type: 'separator'
},
{
label: 'H1',
click() {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editor-channel', 'style-h1')
}
},
{
label: 'H2',
click() {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editor-channel', 'style-h2')
}
},
]
}
]
if (process.platform == 'win32' || process.platform == 'darwin') {
template.push(
{
label: 'Default',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{
type: 'separator'
},
{ role: 'togglefullscreen' },
]
}
)
}
ipcMain.on('editor-channel', (event, arg) => {
console.log("Mensaje recibido del canal 'editor-channel': " + arg)
})
ipcMain.on('file-open', (event, arg) => {
const win = BrowserWindow.getFocusedWindow()
open_file(win)
})
ipcMain.on('file-save', (event, arg) => {
const win = BrowserWindow.getFocusedWindow()
save_file(win, arg)
})
app.on('ready', () => {
globalShortcut.register('CommandOrControl+Shift+S', () => {
const win = BrowserWindow.getFocusedWindow()
win.webContents.send('editor-channel', 'file-save')
})
globalShortcut.register('CommandOrControl+Shift+O', () => {
const win = BrowserWindow.getFocusedWindow()
open_file(win)
})
});
const menu = Menu.buildFromTemplate(template);
module.exports = menuPara consumirlo, tenemos, desde el archivo principal:
const menu = require('./menu')
***
app.whenReady().then(createWindow)
Menu.setApplicationMenu(menu)Y al ejecutar un:
npm run startTenemos:
FAQs sobre menús en Electron.js
- ¿Dónde colocar el menu.js?
- En el raíz del proyecto, junto al main.
- ¿Cómo crear menús diferentes por plataforma?
- Usando:
- if (process.platform === 'darwin') { ... }
- ¿Cómo combinar varios submenús sin romper el template?
- Verifica siempre que cada submenu sea un array válido.
Conclusión
Crear un menú en Electron.js puede ser tan simple o tan avanzado como lo necesites. Lo bonito es que tienes un sistema que te permite mezclar roles nativos, acciones personalizadas, comunicación por IPC, atajos globales e integración con ventanas activas. En mi caso, uso estos patrones para aplicaciones reales —desde editores hasta utilidades— y siempre sigo el mismo flujo: template → archivo externo → roles + acciones → IPC → atajos → multiplataforma.
El siguiente paso es conocer como emplear los atajos de teclado en Electron.
