AUTHOR
Daniel Strong, Frontend Engineer
Daniel is a Frontend Engineer at Codiga.
He is a passionate frontend engineer, teacher, and learner. He has worked on or led several creative projects where he's grown his leadership, management, design, and programming skills.
Note: this is part 2 of "Custom Titlebar for an Electron app with React". We suggest reading part 1 first.
In part 1, we got started with styling buttons to look similar to their native counterparts and ultimately, got them connected with our main
Electron process to be functional.
We're not going to go any further with styling the titlebar here, instead, we'll switch to some other features that you can add to your titlebar or app to give it a fantastic UX.
Detect the current OS build
Something we didn't touch on in the first post was how can you tell which platform you're on. How do you know if you should show your Windows and macOS traffic lights?
Inside your preload.ts
file, we'll detect if the platform is darwin
or not.
contextBridge.exposeInMainWorld("electron", {
isMac: process.platform === "darwin",
});
Now to stop those pesky Typescript errors before they happen, put the following in your preload.d.ts
file.
declare global {
interface Window {
electron: {
isMac: boolean; // this line right here
ipcRenderer: {
sendMessage(channel: Channels, args: unknown[]): void;
on(
channel: string,
func: (...args: unknown[]) => void
): (() => void) | undefined;
once(channel: string, func: (...args: unknown[]) => void): void;
};
};
}
}
Now wherever you are rendering your Windows or macOS titlebar buttons, check your platform, and if it's different return null
.
// Windows
if (!window.electron?.isMac) return null;
// macOS
if (window.electron?.isMac) return null;
Detect a network connection change
Spotify, a prominent music streaming app built with Electron, has a nice feature that we'll implement.
When you're logged in:
When you have no internet connection:
So how can detect when the user has lost their internet connection while on our app?
We'll need to store the current online status somewhere. This will be the value that we read from to update the UI.
const [isOnline, setIsOnline] = useState(true);
If you want to only show something within your titlebar, you can place this logic there. If you want to display this globally, you could use React Context and use the state wherever it's needed. In our app, we wanted to show it in multiple spots, so we used React Context.
Now we need a way to update our state. We use Navigator.onLine to read the current network status and attach online
and offline
event listeners, which will update our state whenever a change in the network state happens.
useEffect(() => {
const updateOnlineStatus = () => {
const currentStatus = navigator.onLine;
if (isOnline && !currentStatus) setIsOnline(false);
if (!isOnline && currentStatus) setIsOnline(true);
};
window.addEventListener("online", updateOnlineStatus);
window.addEventListener("offline", updateOnlineStatus);
return () => {
window.removeEventListener("online", updateOnlineStatus);
window.removeEventListener("offline", updateOnlineStatus);
};
}, [isOnline, setIsOnline]);
Custom about/info/help section
Slack, another prominent app built with Electron, has our second targeted feature.
When building out an app, it can be difficult to place important information that can be reached quickly by new or experienced users.
Next to your user image, there's a help button.
When you click on the button, Slack opens a sidebar with tons of information and direct help links.
Your app might not have that many outbound links and in that case, you would be more like us. We wanted to have this simple functionality with room to expand in the future. Ours currently is more of an about section, but there's plenty of room for growth.
Grabbing the version number dynamically
You might have noticed in the image above that we have the version number listed. We don't hard code that and update it each time. Instead, we grab the version number from our main
Electron process.
Just like we did while detecting network states, decide if you want the version number available globally or in just one place.
// create some default state value
const [appVersion, setAppVersion] = useState("0.0.0");
// this will only run once (when the component has mounted)
useEffect(() => {
// we listen for an 'app-version' message from our ipcRenderer
window.electron?.ipcRenderer.once("app-version", (arg) => {
setAppVersion(arg as string);
});
// we send a message to our ipcRenderer
window.electron?.ipcRenderer.sendMessage("app-version", [""]);
}, []);
Refer to part 1, if you need a refresher on
ipcRenderer
.
Imagine we are calling our friend ipcRenderer
and asking what the current version is (sendMessage
) and waiting to hear their response (on
). Meanwhile our friend, ipcRenderer
, needs to look up what the current version is and tell us.
For the latter part, we'll need to update our main.ts
to include the following:
ipcMain.on("app-version", async (event) => {
event.reply("app-version", app.getVersion());
});
You'll also need to update your preload.ts
file to include the following:
export type Channels = "app-version";
contextBridge.exposeInMainWorld("electron", {
ipcRenderer: {
sendMessage(channel: Channels, args: unknown[]) {
ipcRenderer.send(channel, args);
},
on(channel: Channels, func: (...args: unknown[]) => void) {
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
func(...args);
ipcRenderer.on(channel, subscription);
return () => ipcRenderer.removeListener(channel, subscription);
},
},
});
Now your app can get the current app version.
Conclusion
That's it for how to build a custom titlebar for Electron in a React app.
You should have the knowledge to go out and build any custom features you can think of. Anything from a search input, back and forward buttons, toggle view buttons, or something else.