DEV Community

Cover image for Desktop GUIs for Web Developers
Mitch Stanley
Mitch Stanley

Posted on • Originally published at fullstackstanley.com

Desktop GUIs for Web Developers

This is a cross-post from website - check the original here 😀
Over the past few years I’ve become more interested in making desktop applications. For some context, I’m a web developer with around 15 years of experience. I mostly work with Laravel and Vue.JS but I have dabbled in a whole heap of other languages and frameworks.

I love a good desktop app and where possible I’ll generally prefer having an app rather than visiting a website. I’ve also had to turn websites into desktop apps at my job so I’ve explored a fair few different technologies for this purpose.

I’ve written this blog to share the desktop technologies that I'm currently interested in. Bear in mind, some of these tools I’ve built full apps with and some I’ve only gone through briefly with tutorials. I’ll make this clear throughout the article.

I hope to have given you an idea of what to look for when choosing a desktop application framework. Hint: There is no golden ticket, each tool has pros and cons. All I can give you is my experience with each of them and when you should consider them for your projects.

The tools I'll be reviewing are:

  • Compose Multiplatform
  • egui
  • Electron
    • Ember Electron
    • Quasar
  • Flutter
  • React Native for Windows
  • Tauri

What to look for in a GUI tool

There are almost as many GUI tools as there are frontend Javascript frameworks. So how do you pick one for the project you’re working on?

If you use a Javascript framework for the web, a good place to start is to see if there is a desktop counterpart for that library. For example, Quasar for Vue developers, React Native for React developers, Ember Electron for Ember developers and so on.

Two of the three mentioned above are Electron based tools and I think it’s worth pointing out, if you want something built fast with access to a large community, ecosystem and is regularly updated then Electron is absolutely worth investigating. It gets a lot of gripe because release builds have a large file size, it’s not as fast as native, and generally most apps don’t feel quite right, but these downsides can often be forgiven.

As with all of the tools that I mention below, you have to weigh up various concerns.

  • Who your application is for?—do the users care about it being a wrapper for a web application? Can the tool provide the functionality that your users expect?
  • How complex is the project likely to be?—are there frequent updates that need to be kept up to date with web/mobile counter-parts?
  • The size of the team working on the project—single developer or a large team? Trying to keep two code bases up to date (e.g. a website and a desktop app) for a single developer could literally half their productivity. Not so much of an issue for a small team.
  • How fast does it need to be built? Exploring new technology takes time and some tools are easier to grok than others, have larger communities to help, and have plugins to solve various problems.
  • Accessibility. Unless your making a personal project, you should try to add some level of accessibility to your application. The more the better, but not every tool makes this easy.

With those key points in mind, there are a few additional things to think about

  • What platforms do you want to build for? Not all tools work on every platform. For example, React Native does not build for Linux but does work on iOS and Android. SwiftUI does not build for Linux or WIndows, but the code can be shared with all of the Apple Ecosystem.
  • Distribution and updates. Do you want to distribute via Apple's App Store, the Microsoft Store? Linux has various options for automatic updates including Snaps and AppImages. For MacOS and Windows there’s also options for updates via your own server, or you could let user base to update manually.
  • Support. Is the library actively maintained and how often is it updated?
  • Should you choose boring technology? Small side projects can be a fun excuse to try a new stack, but if you're building a product for a company with customers reliant on stable software then you should probably pick something that’s been battle-tested.
  • Level of native integration. Native isn’t necessarily boolean value. You can use web based technologies for the core application but still support native APIs for window management, menu/tray support, storage, notifications, widgets and more. Electron for example, has great options for all of those features. Some of the newer/smaller libraries tend to fall short in this regard.

Finally, if you’re not familiar with a front end javascript library—maybe because you’re a backend developer—You might also want to look into libraries for programming languages that you’re familiar with. There are often wrappers for existing technologies like GTK, FLTK, Qt. For example, FLTK-rs for Rust, or the GTK3 gem for Ruby.

So, what’s out there?

Here comes the fun stuff. Obviously I can’t go through every single option available, but I will show you what has piqued my interest

Compose Multiplatform

Image.png

Not to be confused with Jetpack Compose, the modern toolkit for building Android apps, JetBrains’ Compose Multiplatform is based on the same technology but allows you to build for Windows/MacOS, Linux and the web.

Compose uses Kotlin and my opinion, this language feels great. So far I’ve run through the Ray Wenderlich tutorial by Roberto Orgiu and I enjoyed the experience. However, there is a lack of resources available for learning it. This tutorial and the official docs and examples are the only things I've come across.

fun main() = Window(
  title = "Sunny Desk",
  size = IntSize(800, 700),
) {
  val repository = Repository(API_KEY)

  MaterialTheme {
    WeatherScreen(repository)
  }
}
Enter fullscreen mode Exit fullscreen mode

As mentioned on the website, it supports keyboard shortcuts, window manipulation, and notifications. It renders with Skia which means your apps will have native performance, however, you will need to build your own ‘Widgets’ or find an existing library if you want your app to actually look native to each platform.

Code sharing between Compose Multiplatform and the Jetpack Compose is possible, too, but I believe most of the UI elements have to be built separately. Still, this is a lot of platform support and I’m genuinely excited to see where this framework goes in the future.

Here’s some example code to get a feel of what it looks like

image.png

import androidx.compose.desktop.DesktopMaterialTheme
import androidx.compose.foundation.ContextMenuDataProvider
import androidx.compose.foundation.ContextMenuItem
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication

@OptIn(ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
fun main() = singleWindowApplication(title = "Compose for Desktop") {
    DesktopMaterialTheme { //it is mandatory for Context Menu
        val text = remember {mutableStateOf("Hello!")}
        Column(modifier = Modifier.padding(all = 5.dp)) {
            ContextMenuDataProvider(  
                items = {
                    listOf(  
                        ContextMenuItem("User-defined Action") {/*do something here*/},
                    )  
                }
            ) {
                TextField(  
                    value = text.value,
                    onValueChange = { text.value = it },
                    label = { Text(text = "Input") },
                    modifier = Modifier.fillMaxWidth()  
                )

            Spacer(Modifier.height(16.dp))  

            SelectionContainer {  
                    Text(text.value)  
                }
            } 
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Positives

  • Works on MacOS/WIndows/Linux and the web.
  • Support for sharing code on Android apps with Jetpack Compose
  • Uses Kotlin
  • Native performance
  • Built in Previews
  • Has tools for automated testing
  • Supported by Jetbrains
  • Actively developed

Negatives

  • Maturity - 1.0 only recently released
  • Small community
  • Only stand alone builds are currently supported (Although there is an Apple App Store PR), no sign of how to handle automatic updates.
  • Small ecosystem (plugins etc)
  • No native UI widgets or theming out of the box

egui

egui is a Rust library and builds natively with Glium (Or Glow) and WASM for the web. For native, It supports MacOS, Linux, Windows.

Out of the Rust GUI libraries available I think this one is my personal favourite. It’s self-described as easy to use and difficult to make mistakes. For someone like myself—who’s more of a visitor to the Rust language—it’s music to my ears.

It’s actively maintained, with a new release out literally an hour ago as of the creation of this sentence.

Here’s a snippet taken from one of the examples along with the newly added context menu support (Right clicking UI elements).

egui::CentralPanel::default().show(ctx, |ui| {
    // The central panel the region left after adding TopPanel's and SidePanel's

  ui.heading("eframe template");
  ui.hyperlink("https://github.com/emilk/eframe_template");
  ui.add(egui::github_link_file!(
      "https://github.com/emilk/eframe_template/blob/master/",
      "Source code."
  ));
  let response = ui.add(Label::new("Test Context Menu").sense(Sense::click()));
  response.context_menu(|ui| {
      if ui.button("Open").clicked() {
          ui.close_menu();
      }
      ui.separator();
      if ui.button("Cancel").clicked() {
          ui.close_menu();
      }
  });

  egui::warn_if_debug_build(ui);
});
Enter fullscreen mode Exit fullscreen mode

Image.png

Positives

  • Works on MacOS, WIndows and Linux and the web.
  • Built with Rust
  • Native performance
  • Actively developed
  • Support for multiple renderers

Negatives

  • Maturity - Not currently at a 1.0 release so the API is unstable and missing features
  • Small community
  • Only stand-alone builds are currently supported (Although there is an Apple App Store PR), no sign of how to handle automatic updates.
  • No ecosystem (plugins etc)
  • No native UI widgets or theming out of the box
  • No live preview

Electron

Image.png

I’ve built two and a half apps with Electron so it’s fair to say I have experienced first hand the positives and negatives of the platform. Electron is a tool that puts web technologies on the desktop via Chrome. With Electron you’ll most likely be writing every part of the app with Javascript or Typescript, although it’s certainly possible to switch this up, for example, 1Password recently switched their desktop app to Electron with a Rust backend.

I’ve used Electron with Ember Electron and with Quasar (Vue.JS). I'll talk more about both individually below, but as a general overview, Electron is fantastic and easy to recommend so long as you can put up with its shortcomings

Positives

  • Works on MacOS, Windows, and Linux
  • Since it’s wrapping a web app, you can likely share most, of the code base with an web app if you have one
  • Large community and ecosystem
  • Support for many forms of distribution, including automatic updates and various app stores
  • Has Chrome’s accessibility features built in
  • Supports multiple windows, and some native components such as dialog boxes, notifications, etc

Negatives

Ember Electron

Image.png

Ember is one of my favourite Javascript frameworks. I’ve built many web projects with it so it was natural for me to try a desktop app with it, too. My apps, Snipline 1 and 2, are both built with Ember Electron so I have a reasonable amount of experience with it.

All of the positives and negatives from the Electron section still apply here, so I’ll comment specifically of the Ember Electron add-on.

With Ember Electron 2, it was tricky to update the Electron dependency, but with the release of Ember Electron 3 the Electron Forge dependency was updated . This means that Electron can be kept up to date separately to Ember Electron. Since Electron gets updated quite regularly it's a much welcome update.

Activity is much slower now with Ember Electron, with the latest release of 3.1.0 back in May, and the community is very small compared to the other available choices. As much as I enjoy the stack, I could not recommend it unless you want to turn an existing Ember app into a desktop app, or are already very productive with Ember.

Quasar

Image.png

Calling Quasar an Electron wrapper is selling it short. It provides many of the benefits of Ember JS, such as file directory conventions and a CLI, but it also adds support for mobile apps, SPAs and it’s own UI framework. Take a look at all of the reasons that make Quasar great on their Why Quasar? page.

I’ve built one desktop app with Quasar for an internal company project, and overall it was a pleasant experience. I much prefer Tailwind CSS to Quasar UI, and there’s nothing stopping you using both except for the additional dependency.

As with Ember Electron, you get all of the benefits of Electron with Quasar, and building the app is as simple as running a command

quasar build -m electron

One difference from Ember Electron is the build module. Ember Electron uses ‘Electron Forge’ whereas Quasar gives you two choices, Packager or Builder. Personally, I’ve used Builder and had no issues besides the teething problems of getting auto updating working on Windows.

Regarding activity, Quasar is very active, with an update to the main repo just for days ago as of writing and plenty recently before that. There are many contributors, and the documentation is great. I think that if you’re familiar with Vue.JS and Vuex then you’re in safe hands using Quasar.

Flutter

Image.png

One of the most impressive things about Flutter is the breadth of devices it supports. From mobile, desktop, to embedded devices. Similar to Compose, it uses Skia to render the UI, so while you’re getting native performance you’re most likely not going to get a native look, at least not out of the box.

Unlike Compose, I was pleasantly surprised when I followed an Android tutorial to build a Windows app and it just worked. Of course, it looked like an Android app, with the default Material theme, but there's nothing to stop you tweaking the theme per device. Take a look at this blog post by Minas Giannekas on how he built Shortcut Keeper and how he themed it for each platform. Truly impressive.

Image

There’s also a large community and ecosystem surrounding Flutter, so you’re unlikely to run out of learning resources.

But Flutter isn’t without it’s shortcomings. There’s a long list of issues in their Github repo, which also speaks for the popularity of the library. Much of the ecosystem is focused on mobile, which means if you wish to make an app work across mobile, desktop, and web, you may have to provide your own functionality for the latter two environments.

There are also complaints that the development of Flutter outpaces the plugins surrounding it. You may need to stay on an older version of Flutter because of plugin compatibility issues.

Positives

  • Native performance
  • Works on MacOS, Windows, Linux, iOS, Android, and Embedded devices
  • Large community and lots of plugins
  • Actively developed and backed by Google
  • Large pool of resources to learn from

Negatives

  • Most of the community and plugins are mobile focused
  • Fast pace of development may mean compatibility issues with plugins

Reactive Native for Windows

Image.png

Since I’ve included a Vue.JS and an Ember JS library, I thought it would only be fair to include a library for React developers, too. React Native is a popular solution for building native apps for iOS and Android and uses Objective-C and Java under the hood for each platform respectively.

For Windows, it renders with Universal Windows Platform (Or UWP for short) which means you really are getting native controls rendered. I couldn’t find any information of how the React Native for MacOS renders, although I’d imagine it’s doing something similar to iOS.

Here’s a quick snippet that I tried starting from the base RNW project.

Screenshot 2022-01-01 165853.png

import React, { useState } from 'react';
import type {Node} from 'react';
import {
    SafeAreaView,
    ScrollView,
    StatusBar,
    StyleSheet,
    Text,
    useColorScheme,
    View,
    Alert,
    Modal,
    Pressable
} from 'react-native';
import {
    Colors,
    DebugInstructions,
    Header,
    LearnMoreLinks,
    ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

const Section = ({children, title}): Node => {
    const isDarkMode = useColorScheme() === 'dark';
    return (
        {title} {children} 
    );
};
const App: () => Node = () => {
    const isDarkMode = useColorScheme() === 'dark';
    const [timesPressed, setTimesPressed] = useState(0);
    const backgroundStyle = {
        [backgroundcolor: isDarkMode ? Colors.darker : Colors.lighter,
    };
    const buttonStyle = {
        [padding: '20px',](padding: '20px',)
    }
    return (
        <SafeAreaView style={backgroundStyle}>
            <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
            <ScrollView
            contentInsetAdjustmentBehavior="automatic"
            style={backgroundStyle}>
                <Section title="React Native for Windows"></Section>
                <Pressable
                    onPress={() => {
                        setTimesPressed((current) => current + 1);
                    }}
                style="{({pressed}) => [
                {
                backgroundColor: pressed ? 'rgb(210, 230, 255)'
                : 'black',
                padding: 10,
                textAlign: 'center'
                },
                styles.wrapperCustom
                ]}>
                {({ pressed }) => (
                    <Text style={() => [ { ...styles.text, textAlign: 'center' }]}>
                        {pressed ? 'Pressed!' : `Count: ${timesPressed}`}
                    </Text>
                )}
                </Pressable>
            </ScrollView>
        </SafeAreaView>
    );
};

const styles = StyleSheet.create({
    sectioncontainer: {
        margintop: 32,
        paddinghorizontal: 24
    },
    sectiontitle:
        fontsize: 24,
        fontweight: '600',
    },
    sectiondescription: {
        margintop: 8,
        fontsize: 18,
        fontweight: '400',
    },
    highlight: {
        fontweight: '700',
    },
});

export default App;
Enter fullscreen mode Exit fullscreen mode

In terms of community, you have the foundation of the mobile RN community to work with, but as with other ecosystems in this article, you probably aren’t going to find much plugin support for desktop at the moment.

Positives

  • Renders natively
  • Share code with React Native mobile apps
  • Build with Javascript or Typescript
  • React developers will feel right at home

Negatives

  • RNW and MacOS are relatively new and not yet stable
  • Smaller community and ecosystem for desktop
  • No Linux support

SwiftUI

Image.png

Having released 2 apps and another on the way, SwiftUI is another tool I have plenty of experience with.

SwiftUI has been designed by Apple to work well on each of their platforms. There are many 'Widgets' that can be shared across each platform so you can write code once and have it run on most devices. For example, context menu's on an iOS device are triggered from a long press, where as on a Mac it's triggered from a right click.

// Taken from the useful app, SwiftUI Companion   
struct ExampleView: View {
   var body: some View {
     Text("Press, hold and release")
       .foregroundColor(.white)
       .padding(15)
       .background(RoundedRectangle(cornerRadius: 8).fill(Color.blue))
       .contextMenu {
         Button("Open") {  print("open...") }
         Button("Delete")  {  print("delete...") }
         Button("More info...") {  print("more...") }
     }
   }
}
Enter fullscreen mode Exit fullscreen mode

A personal favourite feature of mine, which I’ve yet to see in other GUI frameworks, is data binding between multiple windows. Using the @AppStorage property wrapper, you can update a value in one window and have it's value easily sync in another. This is really useful for preferences which are generally in their own window in MacOS apps.

Here’s a truncated example of the power and simplicity of SwiftUI for Mac apps.

import SwiftUI

@main
struct RsyncinatorApp: App {
  @AppStorage('showVisualHints') private var showVisualHints = true

  var body: some Scene {
    WindowGroup {
      ContentView()
    }

    #if os(macOS)
    Settings {
      SettingsView()
    }
    #endif
  }
}

struct SettingsView: View {
  private enum Tabs: Hashable {
    case general, advanced
  }
  var body: some View {
    TabView {
      GeneralSettingsView()
        .tabItem {
          Label("General", systemImage: "gear")
        }
      .tag(Tabs.general)
    }
    .padding(20)
      .frame(width: 375, height: 150)
  }
}

struct GeneralSettingsView: View {
  @AppStorage("showVisualHints") private var showVisualHints = true

  var body: some View {
    Form {
      Toggle("Show visual hints", isOn: $showVisualHints)
    }
    .padding(20)
      .frame(width: 350, height: 100)
  }
}
Enter fullscreen mode Exit fullscreen mode

Here’s the Preferences window that’s generated. If you’re familiar with Mac apps, you should recognise the general layout with the tabbed sections at the top. All this is laid out for you.

Image.png

One major show stopper for many people is that it doesn’t build for Windows and Linux. I also feel it’s only just becoming a real solution as of it's 3rd major release which adds much needed functionality. Functionality such as search and focus states weren't properly supported before so you'd have to write it yourself. There are also bugs that crop up and it's down to Apple's discretion as to when these get fixed.

The community and packages surrounding SwiftUI tend to focus on mobile, however, there are still a reasonable amount of resources for MacOS. If you're interested, take a look at this official tutorial for MacOS for getting started.

Positives

  • Easy to make native Mac apps that look like Mac apps
  • Lots of resources for learning that are applicable for iOS and MacOS
  • Share code between iOS, tvOS, watchOS

Negatives

  • No build for Windows or Linux
  • Bugs are fixed at the whim of Apple
  • Only one major release per year with new functionality
  • Closed source
  • Only fairly recent MacOS versions support it and each of the previous versions of MacOS support fewer features

Tauri

Image.png

Tauri is another fairly new library. It’s a web wrapper and you can use whichever web framework you prefer. There’s an officially supported plugin for Vue.JS, but it is simple enough to add your own. I've had it working with both Ember JS and Svelte.

It’s first major difference from Electron is that it uses your Operating System’s web browser rather than bundling Chrome. This results in fairly tiny file sizes, but at the cost of having to debug issues on different platforms.

The second major difference is that Tauri uses Rust. With Electron you pass messages from main and renderer with Node and Javascript, whereas with Tauri you pass events from the frontend and backend with Javascript and Rust, respectively.

Here’s a snippet from the Tauri documentation of communicating between the two.

import { getCurrent, WebviewWindow } from '@tauri-apps/api/window'

// emit an event that are only visible to the current window
const current = getCurrent()
current.emit('event', { message: 'Tauri is awesome!' })

// create a new webview window and emit an event only to that window
const webview = new WebviewWindow('window')
webview.emit('event')
Enter fullscreen mode Exit fullscreen mode
// the payload type must implement `Serialize`.
// for global events, it also must implement `Clone`.
#[derive(Clone, serde::Serialize)]
struct Payload {
  message: String,
}

fn main() {
  tauri::Builder::default()
    .setup(|app| {
      // listen to the `event-name` (emitted on any window)
      let id = app.listen_global("event-name", |event| {
        println!("got event-name with payload {:?}", event.payload());
      });
      // unlisten to the event using the `id` returned on the `listen_global` function
      // an `once_global` API is also exposed on the `App` struct
      app.unlisten(id);

      // emit the `event-name` event to all webview windows on the frontend
      app.emit_all("event-name", Payload { message: "Tauri is awesome!".into() }).unwrap();
      Ok(())
    })
    .run(tauri::generate_context!())
    .expect("failed to run app");
}
Enter fullscreen mode Exit fullscreen mode

I’ve built and released one app with Tauri and it was fairly painless for a simple app. I used Svelte for the web framework and each installer came out at less than 5MB.

Image

For larger apps, I would most likely struggle to implement certain functionality. The getting started guides are easy enough to follow, but once I started trying to add more functionality I found the overall documentation lacking. There’s also fewer features than Electron which is to be expected since the platform is not as mature and the community not as large.

It supports adding CLI’s to your app which I think is a very cool feature that’s not often built into GUI libraries. You can also embed external binaries which can be very useful if you need to use a command-line tool for functionality in your app. It also supports auto updating for each platform (With Linux supporting AppImage).

Positives

  • Supports auto updating on MacOS, Windows and Linux
  • Build your own companion CLI
  • Integrate external binaries
  • Small distribution file sizes
  • Use any frontend JS framework you prefer

Negatives

  • Fewer features than alternatives
  • Small community and ecosystem
  • Not yet at a stable release
  • Different OS browsers can (and will) behave differently - extra testing required

GUI Library Overview

I thought it would be beneficial to have a casual overview of the differences between platforms, including differences in community size and support.

Releases in the past 6 months gives some indication of activity on each project, and includes beta, dev, and RC releases. This information is taken from each project’s git repository and is checked between 1st July 2021 and 1st January 2022.

As SwiftUI is not open source and other than at WWDC where major changes are announced, we do not get a run-down of changes between Xcode versions, it’s difficult to compare. We do know however that SwiftUI is backed by Apple and appears to be the recommended way of making apps for the Apple ecosystem moving forward.

SwiftUI is also the only platform out of the list that does not support Windows/Linux. It does however have support for iOS, iPadOS, Apple Watch, and Apple TV. If you’re in the Apple ecosystem, it’s definitely something to consider.

Framework/Library Language(s) Native Platform Support Contributors Releases in past 6 months Initial release date Stable release?
Compose Kotlin 💻🪟🐧🤖 64 51 2nd April 2021
egui Rust 💻🪟🐧 89 4 30th May 2020
Electron Javascript 💻🪟🐧 1081 113 12 Aug 2013
React Native for Windows Javascript/Typescript 💻🪟🤖📱 180 49 23 Jun 2020
Flutter Dart 💻🪟🐧🤖📱 957 28 27th Feb 2018
Tauri Rust + Javascript 💻🪟🐧 114 4 18th December 2019

Features

Not all frameworks have every feature. If you’re looking to make an application that relies on specific things such as webcam support then you’ll need to check if it works or you’ll have to code it yourself.

Note that, my Google-foo may fail. I have tried looking through documentation and various resources for each library but unfortunately it’s not always easy to find if a solution exists.

Additionally, these features may get added after this article is published, so do your own research, too!

Here’s a key for the tables below.

  • ✅ - native/first party support
  • 📦 - support via external plugin
  • 🎓 - tutorial/community information available
  • ❓- Unknown (Most likely unavailable)
  • ❌ - unsupported/unavailable

For theming and light/dark mode I’ll be looking at native support for the Operating System’s features. Web wrappers also generally have features that you can use from the browser, e.g. webcam support via JS, which I mention in the table.

Automatic updates for Linux are only available for Electron and Tauri via AppImage. Unfortunately most libraries don’t support over the air updates or only partially support it, and in this case you’ll have to either implement it yourself, or simply prompt the user to install the next update manually by checking a web-hook that you set up and manage.

Framework/Library Context Menus Window Menus Multiple Windows/Window Manipulation Webcam/Microphone Automatic updates Theming, Light and Dark mode Tray
Compose ❌ (issue) 🎓(link)
egui ✅ (basic) ❓(issue) 🎓(link)
Electron 📦 (plugin) ✅ (Via JS) 💻🪟🐧 ✅ (link)
Flutter 📦 (1, 2) 📦 (plugin) 🎓(link)
React Native for Windows Microsoft Store
SwiftUI ✅ (Using AppKit) Mac App Store, Sparkle
Tauri ❌ (JS library work around) (Via JS) 💻🪟🐧 ✅ (Via CSS)

Accessibility

There’s many different levels of accessibility so I thought it would be worth investigating.

When looking at font size, I’m referring to ability to use the Operating System’s font scaling. Most tools are able to implement their own font scaling if they wanted to — or with a bit of additional code.

Interestingly, I tried testing this with Compose on Windows and the font refused to scale up. egui and Flutter worked fine, and browser based libraries will use the web browsers native font scaling.

Framework/Library Voice over Keyboard shortcuts Tooltips OS font size scaling Tab focusing/cycling
Compose ✅ - Mac Only, Windows planned
egui ❌ (issue) ❌ (issue)
Electron 🎓 (link) ✅ (Chromium handles this)
Flutter ❓(link) ✅(link 1, 2) 🎓 (link)
React Native for Windows ❌ (issue)
SwiftUI ✅ (MacOS Montery+)
Tauri ✅ (Via JS) ✅(Via JS)

Final Recommendations

When choosing a library for building a desktop app, I think you have to ask yourself which category your application falls into:

  1. Personal project to solve your own issue
  2. Small scope software with few updates or released as feature complete
  3. Projects targeting developers
  4. Product to be distributed and available to as many people as possible with frequent updates (E.g. a SaaS)
  5. Enterprise - stability and maintainability at utmost importance

For personal and feature complete software, I’d suggest going for the one that appeals the most to you, assuming it has the features you need.

For most other projects, you're most likely going to want to have automatic updates available. That is, unless you want to respond to every support request with 'Can you update to the latest version please’.

It's a real shame that it removes many of the otherwise great libraries from the running. If you can get away with it, you could instead implement a prompt that tells users to download a newer version manually when it’s available. Still, OTA updates are almost a requirement for desktop software today.

There is also a niche for software that only targets Apple devices. Plenty of developers go this route, just take a look at Sketch, Panic, Craft docs, as a few examples. It certainly simplifies development, and if you're already in the Apple ecosystem it's great to scratch your own itch. If this sounds like your situation then SwiftUI is a great choice.

I really like all of these libraries, but Electron is the solution that's least likely to bite you with it's large community, ecosystem and feature set. That said, I'm eager to see the other solution grow in the future.

If you have any thoughts or suggestions for tools I should check out. Please feel free to comment! You can reach me on Mastadon, Twitter, Dev.to, Micro.blog, or comment directly on the original article.

Top comments (0)