Native vs Electron vs PWA for Mac Apps

Native, Electron, or PWA for a Mac app? A practical comparison of runtime, OS integration, performance, and the tradeoffs behind hora Calendar.

10 min read
Three paths from one Mac app idea toward a native Swift app, an Electron app, or an installed web app

Before I wrote the first line of hora, I had a very reasonable alternative: build the calendar UI with web technology, wrap it for desktop, and ship on more than one platform.

I already knew that world better than SwiftUI. It would have made the first prototype easier. It also would have moved the hard work to a different place.

hora was not meant to be a website with a Dock icon. The product I wanted included a persistent menu bar calendar, desktop widgets, system shortcuts, native windows, and a sync process that could stay useful when the main window was closed.

That led me to native Swift and SwiftUI for macOS.

I still do not think native is the correct answer for every desktop app. Electron is not automatically slow, and a Progressive Web App is not automatically a toy. The useful comparison is not which technology wins. It is which set of constraints matches the product you are actually building.

Native, Electron, and PWA are different runtime choices

The labels often get compressed into "native versus web," but they produce three different applications.

Native macOS app

A native hora window is built with SwiftUI and AppKit, compiled for Apple platforms, and distributed as a signed Mac app. The process can call Apple frameworks directly, subject to sandboxing and entitlements.

That does not mean every pixel is free or every API is easy. It means the app and the operating system share the same primary UI and integration frameworks.

Electron app

Electron combines Chromium and Node.js with desktop APIs. Its official process model uses one main process plus renderer processes for application windows. The renderer uses normal web technologies, while preload scripts and IPC expose carefully selected privileged functionality.

This is more than a generic web view. Electron provides modules for windows, menus, dialogs, tray icons, notifications, and other desktop features. It can also load native Node modules when the application needs platform-specific code.

The cost is that the team owns a multi-process desktop runtime, its web application, and any native bridges it adds.

PWA or installed web app

A PWA remains a web application. A manifest, service worker, and browser capabilities can provide installation, offline assets, notifications, and a standalone window.

On current macOS, Safari can also save any webpage as a web app. Apple's Safari web app guide says the result runs independently of Safari, appears in the Dock and Spotlight, has its own website data, and can receive website notifications when the site supports them.

That is a strong option for products whose core experience already works inside browser capabilities. It is not the same distribution or API surface as a compiled Mac app.

The comparison I wish I had at the start

QuestionNative macOSElectronPWA or Safari web app
Primary UI stackSwiftUI and AppKitHTML, CSS, JavaScript in ChromiumWeb platform in the installed browser runtime
Cross-platform reuseMostly within Apple platformsHigh across desktop platformsHighest when the product is already web-first
Direct Apple framework accessYesThrough Electron APIs or native modulesLimited to browser-exposed capabilities
Runtime ownershipApple frameworks plus app codeChromium, Node.js, Electron, and app codeBrowser runtime plus web application
DistributionMac App Store or signed and notarized appSigned desktop package, with optional stores and updaterInstalled from the browser
Best fitPlatform integration is part of the productOne desktop product across operating systemsThe browser can express the complete product

This table is not a scorecard. Each column moves complexity somewhere else.

Performance is an engineering result, not a framework slogan

The original version of this post repeated the familiar claim that a native menu bar app uses about 15 MB of memory while Electron starts somewhere between 80 and 150 MB.

Those numbers are not a useful universal benchmark.

Memory depends on the Electron version, window count, renderer contents, extensions, caches, native modules, and what the native comparison app actually does. A Swift app can allocate badly. An Electron team can profile carefully, defer work, and ship a responsive product.

Electron's own performance guide makes the honest argument: measure the real app, find the resource-hungry work, avoid blocking the main and renderer processes, and load expensive code only when it is needed. The framework maintainers explicitly describe performance as the application's responsibility.

Native still gives hora a smaller conceptual runtime for a Mac-only product. There is no separate Chromium renderer and Node main process to coordinate. That architectural fact matters to me, but it is not a benchmark by itself.

For a fair decision, prototype the hardest screen in every serious candidate and measure:

  • cold launch to the first interactive frame
  • memory after realistic use, not an empty window
  • scroll and drag latency with a full dataset
  • idle CPU and background wakeups
  • package size and update size
  • behavior after sleep, network changes, and multiple displays

The framework name should not substitute for those measurements.

OS integration is possible in Electron, but it changes the work

Another bad argument says Electron cannot integrate with macOS.

It can.

Electron exposes desktop APIs, and its documentation supports native Node modules. A team can bridge into Swift or Objective-C code for functionality that is not available through Electron itself.

The tradeoff is operational. Native modules must be compatible with the target platform and architecture, and they often need to be rebuilt when Electron changes its Node ABI. The application also needs a secure boundary between renderer code and privileged APIs.

If the product needs two small integrations, that may be an excellent trade for sharing most of the UI across macOS, Windows, and Linux. If most of the roadmap is a list of Apple frameworks, the bridge starts becoming the architecture.

PWAs deserve the same nuance. A web app on Mac can have a standalone window, Dock presence, and web notifications. It cannot call arbitrary private or public native frameworks merely because it was added to the Dock. Its ceiling is the capability set the browser exposes.

Why native matched hora

The current hora codebase makes the choice concrete.

It uses:

  • SwiftUI's MenuBarExtra for a calendar that remains accessible when the main window is not active.
  • WidgetKit for agenda and month widgets on the Mac desktop and in Notification Center.
  • AppIntent types for actions such as showing today's events, creating an event, and finding free time through Shortcuts and other system surfaces.
  • APNs background notifications as one signal in the Google Calendar synchronization pipeline.
  • SwiftData for the local cache and Swift concurrency around the sync layer.

The important correction is what hora does not use.

hora is a Google Calendar client. It talks directly to the Google Calendar API and treats Google as the source of truth. It does not use EventKit as a universal adapter for iCloud, Exchange, and local calendars. The first version of this post claimed EventKit was one of the reasons the app had to be native. That was not true of the product I shipped.

Native was still the right choice because the Mac integration itself is a large part of the product, not because every possible calendar framework appears in the target.

A decision map connecting native, Electron, and installed web apps to different levels of Mac integration and cross-platform reuse.

What native cost me

Choosing SwiftUI did not remove complexity. It selected the complexity I wanted to own.

One platform family first

Swift packages and many SwiftUI views can be shared between macOS and iOS, but that does not create a Windows or Linux app. A new desktop platform would need another UI and integration strategy.

Electron would have given me a more direct path to multiple desktop operating systems. A PWA would have made the web the common product surface from day one.

Framework-specific debugging

I have spent days on interactions between SwiftUI scenes, AppKit window state, visual effects, and semantic colors. The appearance switching post is one example.

Native does not mean bug-free. It means the bugs live in the app's code, Apple frameworks, and the boundaries between them.

A smaller hiring and package ecosystem

There are more web developers and web packages than experienced macOS engineers and Swift packages. For a solo product this mainly affects how quickly I can find examples and external help. For a larger team it changes hiring and staffing.

Slower visual iteration in some areas

Web styling and hot reload can be extremely fast. SwiftUI previews help, but complex views backed by real models, entitlements, and app services still require running the application. The feedback loop is not always as smooth as editing a browser-rendered interface.

What Electron and PWA would have cost instead

Electron would have moved more of the work into process boundaries, IPC design, secure preload APIs, runtime updates, and performance profiling. Native integrations would still exist where the product required them.

A PWA would have made deployment and code reuse simpler, but every roadmap item would need to fit the current browser capability model. If a core feature depended on a native framework, I would either drop it, redesign it, or add a separate native companion.

Neither is a failure. They are different product constraints.

When I would choose each option

Choose native macOS when

  • the Mac experience is a selling point, not only a distribution channel
  • menu bar behavior, widgets, Shortcuts, windowing, or other Apple frameworks are central
  • the team is comfortable making Apple platforms the first-class target
  • a smaller direct runtime matters more than one desktop codebase

Choose Electron when

  • the product must ship on several desktop operating systems
  • a mature web application and web team already exist
  • most UI and business logic can be shared
  • the team accepts profiling the runtime and maintaining native bridges where required

Choose a PWA or installed web app when

  • the browser already provides the complete product surface
  • server-driven data and URL-based collaboration are central
  • installation, offline caching, and web notifications cover the desktop needs
  • avoiding a packaged desktop release is more valuable than deep OS integration

The answer can also change over time. A product may begin as a PWA, add Electron when desktop distribution becomes important, then build one native component for a platform-specific feature. Architecture is allowed to follow the product.

The decision for hora

hora is opened throughout the day, but much of its value lives outside the main window. The menu bar, widgets, shortcuts, notifications, and background sync are not decorative extras around a website. They are the reason the dedicated Mac app exists.

That made native a deliberate choice.

If I were building a cross-platform team dashboard, I would likely choose differently. For a focused Google Calendar client that should feel at home on macOS, I would choose Swift again.

You can see that choice in the released product. Try hora Calendar on the Mac App Store, or read how the first version reached the store.

Native, Electron, and PWA FAQ

Is Electron a native Mac app?

Electron produces an installed desktop application and exposes native desktop APIs, but its main UI is rendered with Chromium using web technologies. Platform-specific functionality can come from Electron modules or native Node modules.

Are Electron apps always slow on Mac?

No. Electron adds a Chromium and Node.js runtime, but the user experience depends on the app's architecture and implementation. Measure launch, memory, idle work, renderer responsiveness, and real workflows instead of applying one universal RAM number.

Can a PWA work like a Mac app?

A PWA or Safari web app can run in a standalone window, appear in the Dock and Spotlight, keep separate site data, and support web notifications. It remains limited to browser-exposed capabilities rather than having direct access to every Apple framework.

Should I use SwiftUI or Electron for a Mac app?

Use SwiftUI when Apple platform integration is central and you accept an Apple-first codebase. Use Electron when cross-platform desktop reuse and an existing web stack are more important, then budget for process boundaries, profiling, signing, updates, and any native bridges.

Why is hora Calendar a native macOS app?

hora's product includes a menu bar calendar, Mac widgets, App Intents, native windows, background notifications, and keyboard-focused calendar work. Those system surfaces are a large part of the product, so a direct SwiftUI and AppKit implementation matched its scope.

Related stories

How I Fixed SwiftUI Color Contrast with OKLCH and APCA
Engineering

How I Fixed SwiftUI Color Contrast with OKLCH and APCA

A TestFlight screenshot exposed unreadable calendar tiles. Here is how I rebuilt hora's SwiftUI color system with OKLCH, APCA-style checks, and tests.

Maciej Szamowski10 min read
Google Calendar API vs CalDAV: Which Should You Use?
Engineering

Google CalDAV vs Calendar API: Which Should You Use?

Does Google Calendar support CalDAV? Yes. Compare Google CalDAV with the Calendar API, including sync, OAuth, event data, and multi-provider tradeoffs.

Maciej Szamowski10 min read
Google Calendar API in Swift: The Package I Missed
Engineering

Google Calendar API in Swift: The Package I Missed

I hand-built a Google Calendar API client in Swift, then migrated hora to Google's GTLR library. See the code, tradeoffs, and rate-limit lessons.

Maciej Szamowski10 min read
Mobile Beta Sign-up

Want to stay updated?

Subscribe for information about iOS/iPadOS beta availability.