Electron, the technology that powers Avocode among many other apps, allows you to get a cross-platform desktop application up and running very quickly. If you don't pay attention you will quickly end up in the uncanny valley of apps, though. Apps that don't feel quite in place among your other apps.
This is a transcript of presentation I gave in May 2016 at the Electron Meetup in Amsterdam, updated to take into account API changes. Mind you, this goes into great detail and assumes some familiarity with Electron.
I'm Kilian Valkhof, a front-end developer/UX designer/app developer, depending on who you ask. I have over 10 years of experience creating things for the internet, and built a number of desktop apps in various environments such as GTK and Qt, and of course, in Electron.
The most recent application I built is Fromscratch, a free cross-platform auto-saving note taking
During the development of Fromscratch, I spent a lot of time making sure the app feels great on all three platforms, and finding out how to make that happen in Electron. These are the gotcha's I encountered and how-to's I've come up with.
Getting an app to feel nice and consistent in Electron is not difficult, you just have to make sure you pay attention to the details.
Imagine, for a moment, you release an app. Say, a note taking app. That you tested and used heavily on your Linux machine. And then you get this friendly message on ProductHunt:
Turns out that by default, Copy and Paste commands are not enabled in Electron on macOS!
The way to let people copy and paste things into and out of your application is by providing an Application Menu that contains those commands. In your main.js file, Simply adding the code below will give you glorious, full-HD copy-and-paste:
If you already have a menu, you need to augment it to include the above cut/copy/paste commands.
...or your app will look like this on Ubuntu:
Many, many applications get this wrong unfortunately, because on Windows and on macOS, the icon shown in the task bar or dock is the application icon (an .ico or .icns) and on Ubuntu, it's your window icon that is shown. Adding it is super simple. in your `BrowserWindow` options, just specify an icon:
This will also give you a small icon in the top left of your Windows app.
When you use your browser, word processing software, or any other native application, you will notice that you can't select the text in your menus and other applications like Chrome. One of the ways an Electron app quickly jumps into the uncanny valley is by accidentally triggering a text selection or highlight on what are supposed to be UI elements.
CSS has our back here, though; for any button, menu, or
And the text will simply not be selectable anymore. It will feel much more like a native application. A little tip to check this: simply press ctrl/cmd + A to select all selectable text in your app. Then you can quickly spot the items you still need to disallow selection on.
Really, this is just super inconvenient. On Windows, you need an .ico file, on macOS you need a .icns file and on Linux you need a .png file.
Luckily, the same, normal png can be used to generate the other two icons. Here's the most convenient way:
1. Start with a 1024x1024 pixel PNG. That means we're already 1/3rd of the way done. (Linux, check!)
2. For Windows, run it through icotools to get an .ico:
3. For macOS, run it through png2icns to get an
4. You're done!
electron-packager doesn't need the extension of the icon to pick the correct one for a given platform:
Of course, I only figured that one out after writing my own build script that selected the correct version of the icon. Oh well.
Nothing gives away the inherent browseriness of an Electron app more than a white loading screen. Luckily there are two things we can do to combat that:
If your application has a non-white background color, make sure to specify it in your BrowserWindow options. This won't prevent the square-of-solid-color while your application loads, but at least it doesn't also change color halfway through:
Because we're actually in the browser, we can choose to hide the windows until we know all our resources have been loaded in. Upon starting, make sure to hide your browser window:
Then, when everything is loaded, show the window and focus it so it pops up for the user. You can do this with the "ready-to-show" event on your `BrowserWindow`, which is recommended, or the 'did-finish-load' event on your webContents.
You want to focus it to make the user aware that your application has loaded.
Now, this is one that a lot of "native" apps get wrong as well, and I find it one of the most annoying things ever. It drives me up the wall when a carefully positioned app, upon a next launch, simply resets it position and dimensions back to whatever default the app developer thought would be reasonable. Don't do that.
Instead, save the window’s position and dimensions and restore them on each launch. Your users will thank you.
There are two prebuilt solutions that solve this for you, called electron-window-state and electron-window-state-manager. Both of them work, have good documentation and take care of edge cases such as maximised applications. If you're in a hurry, use these.
You can also roll your own, and that's what I did, based on code I had already made for Trimage a few years ago. It's not a lot of work and it gives you a lot of control. I'll show you:
First off, we need to be able to store the position and dimensions of our application somewhere. You can use Electron-settings which does it very well, but I chose to use node-localstorage because of its simplicity.
If you save your data to `getPath('userData');` electron will store it alongside it's own application settings, in `~/.config/YOURAPPNAME`, or on Windows, in the appdata folder under your user.
As you can see, I add the sensible default in here by providing a fallback value.
Now in Electron, it's not possible to start a window in its
In a perfect world you will only have to save your window state when closing the application, but that will miss all the times the application is terminated for some unknown reason (such as when the power cuts out).
Getting and saving the state on every move or resize event makes sure we always restore the last known position and dimensions.
And the storeWindowState function:
The storeWindowState function has a little gotcha: If you minimise a native maximised window, it will revert to its previous position. This means that when the window state is maximised we want to save that fact, but we don't then want to overwrite the previous (unmaximised) window dimensions so that if you maximise, close, re-open, and unmaximise, you will end up with the position your window had before maximising.
Below are a couple of small, quick tips and tricks.
In general, Windows and Linux users use Ctrl, while macOS users use Cmd for shortcuts. Instead of adding each shortcut (called an Accelerator in Electron) twice, use "CmdOrCtrl" to target all platforms at once.
Using the default system font means that your application can blend in with the rest of the OS. Instead of hardcoding it for each system separately, you can use the following CSS to automatically pick whatever font is the UI font on a system:
"caption" is a keyword in CSS that links to a platform-specified font.
Just like the system font, you can also choose to let the platform determine the colors of your application by using System colors. These are actually deprecated in favor of not-yet-implemented Appearance value type in CSS3, but they're not going anywhere for the foreseeable future.
CSS is an immensely powerful way to lay out applications, especially when you combine flexbox with `calc()`, but don't discount the work done in older GUI frameworks such as GTK, Qt or Apple Autolayout. You can create your app GUI in a similar way by using Grid Stylesheets which is a constraint-based layout system.
Building applications in Electron is a lot of fun and very rewarding: you can get something up and running on multiple platforms in a matter of minutes. If you've never looked into Electron I hope this article intrigued you enough to take a look. Their website has excellent documentation as well as a very cool demo app that lets you try out all the API’s on offer.
If you're already writing Electron applications, I hope the above encourages you to think about how your app runs on all platforms.
Lastly, If you have any additional tips, please share them in the comments!