DEV Community

Cover image for Brow (Why We Built Brow in Swift, Not Electron)
Serhiy
Serhiy

Posted on

Brow (Why We Built Brow in Swift, Not Electron)

A lot of Mac utilities talk about speed.

What matters more is why they feel fast, where the limits come from, and what trade-offs the architecture creates later.

That was one of the earliest decisions behind Brow: build it as a native Swift app from day one.

Not because “native” sounds good in marketing.

Because for the kind of product we wanted to build — launcher, notch layer, system utilities, hardware-aware modules — architecture changes what is possible.

The real question was never just “Swift vs Electron”

The real question was:

Do we want a Mac app that happens to run on macOS, or a Mac app that is built for macOS from the inside?

Electron is a valid choice for many teams. It optimizes for one codebase, faster iteration, and easier cross-platform shipping.

Raycast took a different route. Its app is native, but its extension ecosystem is built around React, TypeScript, and Node so developers can build on top of it quickly.

That is a smart trade-off.

But it is still a trade-off.

We wanted a different one.

Where the difference starts to matter

If you compare products like Brow and Raycast, the real difference is not just “which launcher is faster”.

The deeper difference is this:

  • how much of the product sits directly on native macOS frameworks
  • how much UI or extension logic depends on web tooling
  • how much low-level system behavior you can control directly
  • how much overhead you carry as the product grows

That architecture decision affects everything after it.

Why we chose Swift for Brow

We built Brow in Swift because we wanted the critical path to stay native.

That means no Electron shell, no Chromium runtime, and no browser stack in the core app flow.

For us, that mattered for four reasons.

1. Native system access is not a nice-to-have

Once you move beyond “open apps and search commands”, launcher products start touching awkward parts of the OS:

  • window management
  • clipboard pipelines
  • display control
  • hardware stats
  • menu bar behavior
  • screenshot flows
  • accessibility hooks
  • notch-adjacent UI

These are not areas where web-style abstraction helps much.

They are areas where being close to AppKit, Swift, macOS services, and system APIs matters.

This does not mean every Mac app must be written fully in Swift.

It means that if deep OS integration is central to the product, native architecture compounds in your favor.

2. Extension velocity and product coherence usually pull in opposite directions

This is one of the most interesting trade-offs in tools like Raycast.

A React/TypeScript-based extension system makes extension development much more accessible to web developers.

That helps ecosystem growth.

But it also means the platform has to constantly mediate between:

  • native host behavior
  • extension runtime constraints
  • API boundaries
  • consistency across third-party commands

That model is great for extensibility.

It is not automatically great for tight, system-level product design.

We wanted tighter control over behavior, performance envelopes, and module integration — even if that meant a slower road to ecosystem scale.

3. “Fast” is not only about launch time

People often reduce desktop performance to one thing: how quickly the window appears.

That matters, but it is only the surface layer.

The more important questions are:

  • How much memory does the product carry at idle?
  • What happens when multiple modules are active?
  • How much work is happening in the background?
  • How direct is the rendering path?
  • How many layers are sitting between intent and UI update?

For us, the goal was simple:

keep the stack small, direct, and native.

That is the advantage of building the core this way.

4. The longer-term constraint is complexity, not initial speed

Web-heavy architectures often win early.

They are easier to prototype, easier to staff, and easier to ship across platforms.

But the question we cared about was not:

What gets us to v1 fastest?

It was:

What architecture do we still want when the app becomes much more ambitious?

Because once a launcher becomes a platform, every abstraction starts charging rent.

That rent appears as:

  • more edge cases
  • more memory pressure
  • more runtime boundaries
  • more UI inconsistency
  • more special handling for modules that do not quite fit

Native code is not magically simple.

But for a Mac-only product, it can keep the stack more honest.

Is Brow better than Raycast?

That depends on what you value.

Raycast has a strong ecosystem, a mature extension story, and a smart developer adoption strategy.

But our argument is narrower:

For the kind of product Brow wants to be, staying fully native in the core gives us better foundations.

Not universally better.

Better for:

  • deep macOS integration
  • tighter control over performance behavior
  • lower architectural overhead in the core path
  • building features that feel like part of the OS, not layered on top of it

That was the point of choosing Swift.

The trade-off we accepted

This path is slower.

The ecosystem is smaller.

The tooling is narrower.

Some things are harder than they would be in web stacks.

But you also get something important in return:

a product whose architecture matches its platform.

For Brow, that trade was worth it.


Original posts:

Top comments (0)