Make your macOS app themable
Check how to effortless add theming support to your macOS application with the new ThemeKit framework.
I have been a theme-lover for many years 🌝🌚. I used to spend hours browsing and downloading themes for Linux window managers in the past (mostly Enlightenment, Blackbox and Gnome), and even for WindowBlinds on Windows. With time, most Operating Systems either provided some theming API, or become theme-friendly in some way. Well, that — unfortunately — excludes Mac OS X which, historically, never played really well with theming 😞 (1). For people who love to customize its Mac (like me), not having a good support for theming on the OS is quite disappointing…
After Mac OS X said goodbye to skeuomorphism with OS X 10.10, a lot of things changed UI-wise. The OS partially embraced the “Age of Minimalism”, adopting the much loved and hated flattish iOS 7 inspired design. Cool thing is that Apple was keen enough to add a “Use dark menu bar and Dock” setting! This was a good start… but not enough: if you work at night, you may turn that setting on, you may even use f.lux do reduce the brightness by warming up the colours, but you will still have those bright grey applications 😕 (which is actually where your eyes are focused on).
Disclaimer: I have co-created Flavours in the past, which adds theming capabilities at the OS level (thus, for all apps). That gave me a really deep insight of how things work and how hard it can be to theme on macOS.
While working with the Paw team — Paw is a great (or, the greatest) API tool for Mac — we encountered these same issues again, so we wanted to add some theming capabilities to Paw in order to make it more pleasant to work with at night. There was already some Mac apps which added support for dark mode, but unfortunately, there was no open source framework to do this easily, without having to re-invent the wheel. So this is was our motivation for building ThemeKit: we developed an easy to use framework to add theming support to macOS apps, and shared it with the world!
ThemeKit is a lightweight theming library completely written in Swift 3 that provides theming capabilities to both Swift 3.2+ and Objective-C macOS applications.
Dark Theme for Everyone!
At its simplest and minimal usage, applications can be programmatically themed with a light/dark theme in a single line command:
So, just by bundling ThemeKit and using one line of code, your application would already have a basic dark theme!
Of course, you probably want finer grain about the theme details, so let’s move on…
ThemeKit offers the ability to have colours, gradients, and images that dynamically change depending on the applied theme.
As an example, suppose that a project defines a
ThemeColor.brandColor color. Because it is a theme-aware color, it will resolve to different colours at runtime, depending on the selected theme:
- Light Theme:
ThemeColor.brandColor = NSColor.blue
- Dark Theme:
ThemeColor.brandColor = NSColor.white
We can then use this
ThemeColor.brandColor for changing control colours, to draw on views, create custom controls, etc, and its color will automatically change with the theme.
ThemeImage provide theme-aware gradients and images, respectively.
ThemeKit also allows
NSColor named colours (e.g.,
NSColor.labelColor) to be overridden so that the system provided colours can be replaced with theme-aware colours. These can be defined on the
ThemeColor subclass extension, which will override the system ones, providing theme-aware colours (e.g., defining a
ThemeColor.labelColor will override
ThemeColor is a subclass of
ThemeKit is bundled with 3 “blank” themes:
LightTheme: (default) macOS light appearance
DarkTheme: macOS dark appearance
SystemTheme: automatically alternates between
DarkTheme, reacting to user setting in “System Preferences > General > Appearance”
These bundled themes only differ in the window appearance (light or dark), so absolutely no colours are changed or than that. It is up to the developer to decide what can or not be themed.
Creating New Themes
If you are going to define theme-aware assets (colours, gradients and/or images), before start creating themes for your application, you must first define the application themable assets.
We do this by extending
ThemeImage as below. For this example, we will only be adding three themable assets: two colours and an image.
Defining theme assets
As a bonus, code completion is now working for
ThemeImage classes 🙂
And now, let’s provide two new themes:
- a light theme, natively written in Swift 3 (could have been done in Objective-C as well)
- a dark theme, defined on a plain text file (a
Defining a native theme
Defining a user theme
Of course, the great advantage of user
.theme files is that these are just plain text files! This means that not only you can add theming support to your application, but you can also give your users the option and power to design custom themes!
Additionally, if you have a user theme (
.theme) applied, ThemeKit automatically reloads and applies it when it changes on disk:
Using our theme-aware assets
On our view controller, let’s change the image and colour of
Putting it all together
Now we just need to setup and initialize ThemeKit in
If you plan to add theming capabilities to your application, you should definitively consider giving ThemeKit a try!
Check the ThemeKit project page where you can find all the details about the framework: installation, configuration, customization, documentation, etc. You can also download a pre-built binary of the ThemeKit Demo project which showcases some of the ThemeKit features.
Useful links & material
- SampleApp.app from this article
- ThemeKit Demo.app
- ThemeKit Project Page
- ThemeKit Documentation
(1) Curiosity: While theming and OS X never played well, the same wasn’t true for its predecessor, the Classic Mac OS. At the time, Apple bundled Mac OS 8 and 9 with Appearance Manager, a native UI theming API. And on top of that, there was a commercial and super popular app: Kaleidoscope! Later, on OS X, most popular theming solutions were ShapeShifter (10.2–10.5) and Flavours (10.7–10.10), but all of them needed to hack the OS in order to accomplish the goal.