Learn how BrowserWindow creates and controls app windows, show them without a white flash, react to window events and safely keep a window reference.

Bishal Biswas
Self Learner
Windows are the heart of your app
Almost everything your users see lives in a window. In Electron, every window is a BrowserWindow: a real operating-system window with a Chromium page inside it.
Think of a picture frame. The frame is the BrowserWindow: it has a size, a position, a title bar and close buttons. The picture inside is the web page, which Electron calls the web contents. You can move and resize the frame without touching the picture, and you can swap the picture without replacing the frame.
In this lesson you will create windows, show them nicely, control them from code, and react when the user moves, focuses or closes them.
Creating a window
You already know the basic shape from Your First Electron App:
// main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
win.loadFile('index.html');
return win;
}
app.whenReady().then(createWindow);
new BrowserWindow({...})creates the window. The object you pass is the options: size, title, icon and much more. Window Options: Size, Position, Title and Icon covers them in detail.webPreferencesholds the options for the page inside the window, like which preload script to run.win.loadFile('index.html')puts a page inside the frame.- Remember: windows can only be created after the app is ready. See The App Lifecycle.
The frame and the picture: win and win.webContents
The object you get back, win, controls the frame. Its webContents property controls the page:
// main.js
win.setTitle('Hello'); // the frame: change the title bar
win.maximize(); // the frame: fill the screen
win.webContents.openDevTools(); // the page: open its DevTools
win.webContents.reload(); // the page: reload it
When you are not sure where a method lives, ask yourself: “Is this about the window, or about the web page inside it?”
Loading a page is asynchronous
loadFile() (and its cousin loadURL()) return a Promise. It is fulfilled when the page has finished loading, and rejected if loading fails. You can wait for it:
// main.js
app.whenReady().then(async () => {
const win = new BrowserWindow({ width: 800, height: 600 });
try {
await win.loadFile('index.html');
console.log('Page loaded!');
} catch (error) {
console.error('Could not load the page:', error.message);
}
});
If you misspell the file name, you will see an error containing ERR_FILE_NOT_FOUND instead of a mystery blank window. More on loading in Loading Pages, Styles and Images.
Showing the window without a white flash
When a window opens, it appears straight away, but the page takes a moment to draw. For a split second the user may see an empty white rectangle. On a dark-themed app that flash is very noticeable.
There are two simple fixes, and you can use both together.
Fix 1: wait for ready-to-show
Create the window hidden, and show it when the page has drawn its first frame:
// main.js
const win = new BrowserWindow({
width: 800,
height: 600,
show: false, // create it hidden
});
win.once('ready-to-show', () => {
win.show();
});
win.loadFile('index.html');
show: falsecreates the window without displaying it.- The
ready-to-showevent fires once the page has been drawn, so showing it then gives a clean appearance. once(instead ofon) means “listen for this event only one time”. We only need to show the window the first time, sooncekeeps things tidy.
Fix 2: set a background colour
Give the window the same background colour as your page:
// main.js
const win = new BrowserWindow({
width: 800,
height: 600,
backgroundColor: '#1e1e1e',
});
Now, even before the page draws, the window is dark instead of white. The default background is white (#FFF).
Tip: For a page that loads very slowly (for example, one that fetches data first), a
backgroundColoris often nicer thanready-to-show. The user sees the window immediately in the right colour, instead of waiting with nothing on screen.
Controlling a window from code
Your win object has methods for everything a user can do with a window, and more:
| Method | What it does |
|---|---|
win.show() / win.hide() |
Show or hide the window |
win.focus() |
Bring it to the front and give it keyboard focus |
win.minimize() |
Minimize to the taskbar or Dock |
win.maximize() / win.unmaximize() |
Fill the screen, or go back |
win.restore() |
Restore from minimized |
win.setFullScreen(true) |
Go full screen |
win.center() |
Move to the middle of the screen |
win.close() |
Close, as if the user clicked the close button |
win.destroy() |
Close immediately, skipping the close event |
And methods that ask questions:
| Method | Returns |
|---|---|
win.isVisible() |
true if shown |
win.isFocused() |
true if it has keyboard focus |
win.isMaximized() / win.isMinimized() |
true or false |
win.getTitle() |
The current title text |
win.isDestroyed() |
true if the window has been closed and is gone |
Try a few in main.js:
// main.js
app.whenReady().then(async () => {
const win = new BrowserWindow({ width: 800, height: 600 });
await win.loadFile('index.html');
console.log('Title:', win.getTitle());
console.log('Maximized?', win.isMaximized());
setTimeout(() => win.maximize(), 2000); // after 2 seconds, fill the screen
});
Listening to window events
Windows emit events when things happen to them. Listen with win.on(...):
| Event | Fires when |
|---|---|
ready-to-show |
The page has drawn and the window can be shown |
focus / blur |
The window gains or loses keyboard focus |
show / hide |
The window is shown or hidden |
maximize / unmaximize |
The window is maximized or goes back |
minimize / restore |
The window is minimized or restored |
resize / move |
The window is being resized or moved |
close |
The window is about to close (can be cancelled) |
closed |
The window has closed |
Here is a window that puts its focus state in the title bar:
// main.js
win.on('focus', () => win.setTitle('My App (active)'));
win.on('blur', () => win.setTitle('My App (in the background)'));
Click another app, then click back. The title changes each time.
Note: The page’s
<title>tag also sets the window title, and it wins whenever the page loads or changes its title. Set titles from code after the page has loaded, or see Window Options for how to stop the page from changing it.
Asking before closing
The close event fires before the window closes. Calling event.preventDefault() cancels the close. This is how apps ask “Do you want to close without saving?”:
// main.js
const { dialog } = require('electron');
let hasUnsavedChanges = true; // in a real app, the page would tell main this
win.on('close', (event) => {
if (!hasUnsavedChanges) return; // nothing to lose: let it close
const choice = dialog.showMessageBoxSync(win, {
type: 'question',
buttons: ['Close anyway', 'Cancel'],
defaultId: 1,
cancelId: 1,
message: 'You have unsaved changes. Close anyway?',
});
if (choice === 1) {
event.preventDefault(); // the user picked "Cancel": keep the window open
}
});
dialog.showMessageBoxSync shows a native message box and returns the index of the button the user clicked: 0 for “Close anyway”, 1 for “Cancel”. You will learn all about dialogs in Native Dialogs: Open, Save and Message Boxes.
Keeping a reference to your window
Often other parts of main.js need to reach the main window, for example a menu item that sends it a message. Store the window in a variable outside the function:
// main.js
let mainWindow = null;
function createWindow() {
mainWindow = new BrowserWindow({ width: 800, height: 600 });
mainWindow.loadFile('index.html');
mainWindow.on('closed', () => {
mainWindow = null; // the window is gone: forget it
});
}
Setting it back to null in closed matters. Once a window is closed, its object is destroyed, and calling any method on it throws an error:
TypeError: Object has been destroyed
When you are not sure, check first:
// main.js
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.focus();
}
Finding windows
Electron also has static methods, called on BrowserWindow itself rather than on one window:
// main.js
BrowserWindow.getAllWindows(); // an array of every open window
BrowserWindow.getFocusedWindow(); // the window with focus, or null
BrowserWindow.fromId(1); // the window with this id, or null
Every window also has a numeric win.id, unique while the app runs. And BrowserWindow.fromWebContents(webContents) finds the window that holds a given page, which you will use with IPC to work out which window sent a message.
You can open as many windows as you like by calling createWindow() again. Working with Multiple Windows shows how to manage them well.
Common mistakes
“Object has been destroyed”
You called a method on a window after it closed. Set your variable to null in the closed event, and check !win.isDestroyed() before using a window that might be gone.
The window never appears
You created it with show: false but forgot to call win.show() (or the ready-to-show listener was added with a typo in the event name). Check both.
A window that will not close (and an app that will not quit)
If your close handler always calls event.preventDefault(), the user cannot close the window, and app.quit() cannot close it either. Only prevent the close when you really need to, and let it through otherwise.
Setting the title too early
win.setTitle('My App') before loadFile() gets overwritten by the page’s <title>. Set the title in the HTML, or after the page has loaded.
Quick recap
new BrowserWindow(options)creates a window, after the app is ready.wincontrols the frame.win.webContentscontrols the page inside it.loadFile()andloadURL()return Promises. Catch them to see loading errors.- Avoid a white flash with
show: falseplusready-to-show, and withbackgroundColor. - Control windows with methods like
show,focus,maximize,minimize,centerandclose. - Listen to
focus,blur,resize,closeandclosed.event.preventDefault()inclosecancels it. - Keep a reference in a variable, set it to
nullonclosed, and checkisDestroyed(). BrowserWindow.getAllWindows(),getFocusedWindow()andfromId()find windows.
// main.js: create, show, watch and safely close a window
const { app, BrowserWindow, dialog } = require('electron');
const path = require('path');
let mainWindow = null; // a reference other code can use
let hasUnsavedChanges = false; // pretend the page reports this
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 600,
show: false, // hidden until the page has drawn
backgroundColor: '#1e1e1e', // no white flash on a dark page
webPreferences: { preload: path.join(__dirname, 'preload.js') },
});
// --- Show it cleanly, once the first frame is ready
mainWindow.once('ready-to-show', () => mainWindow.show());
// --- React to the user
mainWindow.on('focus', () => console.log('focused'));
mainWindow.on('blur', () => console.log('in the background'));
mainWindow.on('maximize', () => console.log('maximized'));
// --- Ask before closing if there is unsaved work
mainWindow.on('close', (event) => {
if (!hasUnsavedChanges) return;
const choice = dialog.showMessageBoxSync(mainWindow, {
type: 'question',
buttons: ['Close anyway', 'Cancel'],
defaultId: 1,
cancelId: 1,
message: 'You have unsaved changes. Close anyway?',
});
if (choice === 1) event.preventDefault();
});
// --- Forget the window once it is gone
mainWindow.on('closed', () => { mainWindow = null; });
mainWindow.loadFile('index.html').catch((error) => {
console.error('Could not load the page:', error.message);
});
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
else if (mainWindow && !mainWindow.isDestroyed()) mainWindow.focus();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
Previous
The App Lifecycle: ready, window-all-closed and activate
Next
Window Options: Size, Position, Title and Icon
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready