Building the Shortcut Keeper app for both platforms, taking advantage of Flutter’s desktop UI capabilities.
Edit 1: This post has made it to Hacker News, so you can check the discussion here as well.
It is fair to say that using Flutter desktop to create the Shortcut Keeper app went great!
In the previous post, I focused on how I used Flutter and its ecosystem to develop and ship the app in such a short timeframe (~1 month for the macOS version and about two weeks to adapt it for Windows).
Here, I will try to present another huge benefit of using Flutter desktop: the ability to build an app with a user interface that matches the underlying platform’s design standards.
As you can see, Shortcut Keeper is built to be an adaptive app for desktops, boasting a different UI design for macOS and Windows, while using a single codebase:
I will also include some tips on how to implement these designs, and provide useful resources.
Why build an adaptive desktop app
Users expect the apps on their system to behave similarly to other apps they use daily. There are specific norms and idioms on each platform that an app developer can easily follow and replicate, and improve the user’s experience with the app.
A classical example of this is how on Windows, the “confirm” button is traditionally placed on the left side, while it’s the opposite on macOS:
Another one is keyboard shortcuts, of course. When selecting the appropriate shortcuts for your app, you need to keep in mind that Command ⌘ (or Meta) is the main modifier button in macOS, while Ctrl (Control) is the main one for Windows.
There are also differences in how keyboard shortcuts are displayed, and which ones are traditionally used for certain actions, like
⌘ , (Command-comma), for the app’s Preferences on macOS:
These may seem to be small things, but they eventually add up to make a difference on the user’s efficiency while navigating throughout the app.
You can of course configure this stuff and build an adaptive UI for your app with Flutter’s Material UI or your own custom design framework:
But, you can also take it one step further and try to match each platform’s design system to build a native-looking app.
Why build a native-looking UI for your app?
A native-looking UI is simply a design for your app that not only respects the adaptive guidelines we set previously, but also tries to get your app closer to a native feel for each platform.
It tries to provide users of your app with a familiar interface that they can easily navigate and be more productive.
Another argument for building apps with native-looking design, is that you immediately take advantage of each platform’s best design practices. Just as you might use the Material or Cupertino libraries for building mobile apps for Android and iOS, it makes sense to adopt the corresponding design system for desktops.
In the case of a desktop app, that means following Apple’s Human Interface Guidelines and Microsoft’s Fluent Design language. I feel that you are always better off learning from the best and following their battle-tested implementations.
Finally, it helps with building trust. A macOS user that is accustomed to the design of apps like Finder, Notes, or Safari, will find herself at home with an app that features similar buttons, toolbars, text fields, etc.
It makes even more sense in the context of desktop apps, as most users don’t use multiple operating systems, but are tied to one for a long time.
Two widget libraries to save a lot of work
As I explained in the previous post, I wouldn’t be able to implement this design and behavior, without these highly recommended packages:
They both provide a wealth of useful UI widgets, like buttons, dropdowns, list tiles, etc., and a basic layout scaffold to work from. Each one tries to adhere closely to the respective platform’s design language, so the real challenge was to:
- Get both UI libraries mixed in the same codebase.
- Handle all the conditional logic of the integration.
- Extend or customize each library in cases where some functionality or component is missing.
So, let me list some more examples of platform-specific UI design, and how I used these libraries alongside Flutter, to build Shortcut Keeper’s UI.
Comparing UI elements on macOS and Windows
Here is the home screen of the Shortcut Keeper app in both platforms:
Let’s see the main differences between the two of them and how each UI component differs between the macOS and Windows versions.
Title bar and icon buttons
At the top section of the app’s UI:
- The app’s main action buttons are at the very top on macOS, while on Windows they are placed inside the main section. This conforms to platform norms, as shown above on the Apple Notes app, while on Windows, the top-level chrome is usually left blank and only shows the app’s name and icon.
- The icon buttons (Delete, Edit, Add) feature an additional text label on Windows. On the macOS version, the design is more colorful.
Buttons, text fields and checkboxes
When adding a new shortcut, there is the option to manually select the shortcut’s keyboard combination. Here is that screen on both platforms:
We can see here that:
- The “Save Shortcut” button appears on the left on Windows, in contrast to macOS, where it’s on the right.
- Buttons on Windows are more rectangular than on macOS.
- Checkboxes, the dropdown button, and text fields also have a different design for either platform.
Show/hide dialog animation
Animations are used mostly when navigating the app’s screens or when dialogs come into view. They are pretty different between the two platforms.
On Windows, you can see:
- The various screens have a back-to-front zooming in animation, consistent with other Microsoft apps. Also notice the cool moving effect of the vertical line at the very left of the sidebar.
- The dialogs appear with a similar animation, and also apply a shadow backdrop when active.
- Navigation animations are snappier and more immediate.
- Dialogs have a bouncing animation when they appear and apply a whitened out backdrop to the rest of the UI.
Font and typography
I chose to use the default font family of each system, so it is San Francisco on macOS and Segoe UI on Windows:
I decided to keep the sidebar and the main area with the list of shortcuts pretty much the same, to achieve a consistent look between them.
There are, of course, other small differences to talk about; even the app’s logo is different, as it wouldn’t make sense to use the Command symbol in Windows:
Now, to explain a bit about how I went about bringing these different designs to life with Flutter.
Splitting the app into two
The initial step was to get the app’s
main.dart to decide which type of app to build: a
MacosApp for macOS or a
FluentApp for Windows. This is done with a simple conditional:
This is required in order to be able to use each library’s custom widgets, just like how Material widgets expect to have at least one Material ancestor, when using Flutter’s default Material UI.
Mixing macOS and Windows widgets: the platform-aware widget
For UI components that are available as widgets in both packages, I opted to create my own “platform-aware” widgets. For example, here is the simplest implementation of such a widget, a
It is essentially a simple fork to select which library widget to use for each platform, with maximum code reuse. This renders differently for each platform:
This way we can use the
PlatformTextField widget anywhere we need, and use the same properties and values, like the controller or a placeholder, to increase our code reuse:
The same can be done for other components, like the confirm button, where we use
This is pretty convenient but, since both libraries are still in development phase, there are some UI elements that are not available in either one, or both.
Extending and customizing the UI libraries
For example, this was the case with the dropdown button. There was no macOS-style widget available in the macos_ui library, so the app initially used the Material-themed
What I did, and what we can do in generally in such cases, is to extend the library, based on Flutter’s basic widgets.
This means that we need to adapt it to the platform’s guidelines, in this case the Pop-Up button from Apple’s Human Interface Guidelines. It’s the classical select button used system-wide.
So, we implement a
MacosPopupButton widget, based on Flutter’s
DropdownButton widget, but with:
- Different size, padding, drop shadow, borders, etc.
- The characteristic double caret icon on the right.
- MacOS-specific colors.
- No scrollbar when open.
- When the menu is open and there are items that are not visible, show an up or down caret, at the top or bottom of the menu.
The correct colors, values, and behavior to do this replication can be found either in the design guidelines, or we can check them out in other apps on macOS.
This way, we have another native-looking component that we can re-use across our app:
The final result
In the end, using the above packages and techniques, I was able to create an app that functions exactly the same on both platforms, while respecting their design principles and norms.
Here are some comparison screenshots for the app on macOS and Windows:
I really hope that this article and its resources prove useful for anyone looking to build an app with Flutter desktop!In this whole endeavor, I came across some great guides and packages:
- Building adaptive apps from Flutter docs.
- Apple’s macOS Design Themes.
- Microsoft’s Design guide for Windows apps and XAML Controls Gallery.
Packages/Open Source projects:
- Flutter Gallery’s repo.
- flutter_platform_widgets, which does a similar job in choosing widgets according to the platform, but for iOS and Android (Cupertino and Material UI).
- macos_ui and fluent_ui, which were used extensively here.
- bitsdojo_window, a really useful package for customizing the appearance of the window for each platform.
- msix, to build the required installer of the app for the Microsoft Store.
Thanks for reading! Here is a photo of my cat: