Skip to content

Electron - System Dialogs

Desktop applications frequently need to interact with the user through standard system dialogs, such as those for opening files, saving files, or displaying alerts and confirmations. Electron’s dialog module provides a straightforward way to use these native system dialogs, ensuring a consistent user experience with the operating system.

Using native dialogs is preferable to custom HTML-based dialogs for these standard interactions as they are familiar to users and integrate well with OS features.

The dialog module can only be used from the main process. If you need to trigger a dialog from a renderer process, you must use IPC to request the main process to show it. Most dialog methods are asynchronous and return Promises.

Let’s create an example app that allows the user to open a text file and display its content.

Your main.js will handle the dialog and file reading:

const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs').promises; // Use the promise-based fs module
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(createWindow);
// Handle 'dialog:openFile' IPC message from renderer
ipcMain.handle('dialog:openFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
title: 'Open Text File',
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt', 'md', 'js', 'html', 'css'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (canceled || filePaths.length === 0) {
console.log('File open dialog canceled or no file selected.');
return null; // Or throw an error, or return an object indicating cancellation
}
const filePath = filePaths[0];
try {
const content = await fs.readFile(filePath, 'utf-8');
return { filePath, content };
} catch (error) {
console.error('Failed to read file:', error);
dialog.showErrorBox('Error Reading File', `Could not read file: ${error.message}`);
return null; // Or re-throw, or return an error object
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

Create preload.js to expose the IPC call to the renderer:

const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
});

And your index.html to trigger the dialog and display content:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>System Dialogs - File Reader</title>
<style>
body { font-family: sans-serif; margin: 20px; }
#fileInfo { margin-bottom: 10px; font-style: italic; }
#fileContent { white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }
</style>
</head>
<body>
<h1>Text File Viewer</h1>
<button id="openFileButton">Open File</button>
<div id="fileInfo">No file selected.</div>
<pre id="fileContent">File content will appear here.</pre>
<script>
const openFileButton = document.getElementById('openFileButton');
const fileInfoDiv = document.getElementById('fileInfo');
const fileContentPre = document.getElementById('fileContent');
openFileButton.addEventListener('click', async () => {
try {
const result = await window.electronAPI.openFile();
if (result && result.content !== null) {
fileInfoDiv.textContent = `Displaying: ${result.filePath}`;
fileContentPre.textContent = result.content;
} else if (result === null) {
// Error handled by main process (dialog.showErrorBox)
fileInfoDiv.textContent = 'Failed to open or read file.';
fileContentPre.textContent = '';
} else {
// Dialog was canceled
fileInfoDiv.textContent = 'File open canceled.';
fileContentPre.textContent = '';
}
} catch (error) {
console.error('Error in renderer trying to open file:', error);
fileInfoDiv.textContent = 'An unexpected error occurred.';
fileContentPre.textContent = '';
// Optionally, show a more user-friendly error in the UI
}
});
</script>
</body>
</html>

When you run the app (electron .) and click the “Open File” button, a native file open dialog will appear. Its appearance will match your operating system’s standard file dialogs. After selecting a text file, its content will be displayed in the application window.

The dialog module offers several types of dialogs:

  • dialog.showOpenDialogSync([browserWindow, ]options) and dialog.showOpenDialog([browserWindow, ]options): For opening files and directories. The async version returns a Promise.
  • dialog.showSaveDialogSync([browserWindow, ]options) and dialog.showSaveDialog([browserWindow, ]options): For prompting the user for a file path to save to. The async version returns a Promise.
  • dialog.showMessageBoxSync([browserWindow, ]options) and dialog.showMessageBox([browserWindow, ]options): For displaying message boxes (alerts, confirmations, prompts with custom buttons). The async version returns a Promise resolving to an object with the user’s response.
  • dialog.showErrorBox(title, content): A simple dialog to display an error message. This is a synchronous operation.

Always prefer the asynchronous versions of dialog methods in modern Electron development to avoid blocking the main process. Remember to handle both successful outcomes and cancellations/errors from the Promises returned by these methods.