A new Swift GUI framework in town. It's Flutter's.
The whole Flutter framework — widgets, rendering, painting, gestures, animation, semantics — ported to Swift, running on the real Flutter engine. Same declarative model, same layout algorithm, same widget catalogue. No Dart VM, and no rewrite of the hard parts underneath.
Here to build something rather than read the argument? Start here — install, write an app, run it.
Two good things that had never met
Flutter's framework is one of the best pieces of UI design of the last decade. The declarative, React-style model where the tree is a function of state. Widgets as cheap, immutable descriptions with elements underneath doing the reconciling. And the layout engine — a single-pass, constraint-based algorithm where constraints go down and sizes come up, which is genuinely clever and gets you predictable layout in linear time. Fifteen years of accumulated design, and it works.
Swift is a wonderful language: fast, safe, expressive, ahead-of-time compiled, with real concurrency and an ownership story that keeps getting better. It is also open source and runs on Linux and Windows.
But if you write Swift and want a graphical application, your options thin out fast. SwiftUI is superb and closed — Apple's platforms only, no source, and nothing to port. Bindings to GTK or Qt exist and are useful, but they wrap imperative C and C++ toolkits; you get someone else's widgets and someone else's programming model, not a declarative one. Outside Apple's walls, a Swift developer who wants the model SwiftUI made everyone want has had very little to reach for.
Ported above, unchanged below
The dividing line is exactly where Flutter already draws it. Everything Dart did, Swift now does. Everything C++ did, C++ still does.
dart:ui, in Swift — Offset, Size,
Rect, Paint, Canvas, Paragraph: the same API surface, bridged to the
engine's own implementationThere is no Dart VM anywhere in that stack. Where the engine would normally start a Dart isolate and call into it, it starts a Swift runtime delegate instead and calls into that. From the engine's point of view almost nothing changed, which is the property that makes the whole thing tractable.
Why we kept the C++ engine
We are not the first to notice that Flutter's framework and Swift belong together. Shaft is a Swift port of Flutter's framework too, and a good project — it reached the same conclusions we did about what Swift brings, right down to naming native multithreading, direct graphics access and ARC as the reasons. Two independent projects converging on the same argument is worth more than either of us asserting it.
Where we differ is the layer below. Shaft replaces the engine with its own backend — Skia over SDL, with direct access to Metal, OpenGL and DirectX. That buys real things: a much smaller dependency, low-level graphics control, and multi-window support that falls out naturally. We took the other road and kept Flutter's engine, for three reasons.
01Text is the part nobody should rewrite
Drawing a rectangle is easy. Laying out a paragraph is not. Real text needs shaping through HarfBuzz, line breaking through ICU, bidirectional reordering, font fallback across scripts, emoji clusters, ellipsis, selection geometry and accessibility bounds — and it needs to be right in Arabic and Thai and Japanese, not only in English. Flutter's engine has years of work in that layer and ships it tuned. Inheriting it is the single largest thing keeping the engine buys.
02The embedders are the platform work
The engine already carries embedders for Android, iOS and macOS, Windows, Linux and more. Each one is the unglamorous, essential work of a platform port: window and surface management, input, IME, accessibility, vsync and frame scheduling. Our Linux desktop host is the engine's own GTK embedder, running in Swift mode — so window management and input come from the exact code path a real Flutter Linux app uses. Every embedder that exists is a platform we can reach without writing that layer ourselves.
03Upstream keeps improving without us
Impeller, rasterizer work, text fixes, platform updates — all of it lands in the engine, and all of it is ours by tracking a fork rather than by reimplementing. The framework is the part we wanted to own, because that is the part the language shows through. The renderer is the part we wanted to inherit.
The cost is honest: the engine is a large dependency, and we live with its architecture rather than around it. We think that is the right trade for a framework meant to run real applications on many platforms — but it is a trade, and Shaft's answer is a legitimate one for people who want the graphics layer in their own hands.
The properties the framework gains
A port is only worth the effort if the new language gives you something the old one could not. Three things, in order of how much they mattered.
01Real threads that share memory
Dart's concurrency model is isolates: separate heaps, no shared mutable state, messages copied between them. That is a genuine feature — it makes data races structurally impossible — and for most app code it is the right trade. But some programs cannot pay it. A video pipeline, a compositor, an audio engine, anything moving large buffers between threads: a 4K frame crossing an isolate boundary as a copy is not a design, it is a budget you have already spent.
In Swift that is ordinary code — a lock, an atomic, a class shared between
threads, with Sendable and actors when you want the compiler
checking your work. To be precise about what did not change: the
framework's element tree is still single-threaded, exactly as in Flutter.
You build UI on one thread. The difference is that everything
around the UI can be genuinely concurrent and still share memory
with it.
02C and C++ at zero cost, with no bindings layer
Swift imports a C header as a module. You write import SomeCLibrary
and call its functions like any other — no binding declarations, no code
generator, no marshalling step, no second description of a struct you have
already described in a header. Structs arrive as structs, function pointers
as closures, and C++ interop is a build setting.
Dart reaches C through dart:ffi, which works, but is a bridge:
bindings declared or generated, memory managed by hand through
Pointer, and rules about which isolate a callback may run on.
For an app calling one library that is a small tax. For anything built on a
stack of native libraries it becomes most of the program — and native
libraries are where the interesting capabilities live.
03No VM in the process
Swift compiles ahead of time to native code with reference counting: no interpreter, no JIT warm-up, and no tracing garbage collector deciding when to pause. Startup is a native binary starting. The honest version is that ARC is not free either — retain and release traffic costs something — but it is deterministic and visible in a profile, which is a different kind of problem from a pause you did not schedule.
If you know Flutter, you already know this
Swift and Dart are close relatives — classes, generics, closures, named
arguments, optionals, trailing closures. The port is a near-mechanical
translation rather than a reimagining, which means Flutter's documentation,
its widget catalogue and fifteen years of accumulated answers still apply.
Here is flutter create's counter app, both ways.
class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('You have pushed the button…'), Text('$_counter'), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: Icon(Icons.add), ), ); } }
class _MyHomePageState: State<StatefulWidget> { private var _counter = 0 private func _incrementCounter() { setState { _counter += 1 } } override func build(_ context: any BuildContext) -> Widget { let homePage = widget as! MyHomePage return MaterialScaffold( appBar: MaterialAppBar(title: homePage.title), body: Center( child: Column( mainAxisAlignment: .center, children: [ Text("You have pushed the button…"), Text("\(_counter)"), ] ) ), floatingActionButton: MaterialFloatingActionButton( onPressed: { [weak self] in self?._incrementCounter() }, child: Icon(CupertinoIcons.add) ) ) } }
Same structure, same lifecycle, same setState. The differences
are Swift's, not ours: override func instead of
@override, a capture list on the closure, and enum cases that
shorten to .center because the type is already known.
There is one addition. Because Swift has result builders, every container
also takes a trailing closure, so if, for and
switch work directly inside a widget tree — no spread operators,
no .map().toList(). Both spellings compile to the identical
tree, and a lint keeps them from drifting apart.
“So why not just write Dart?”
If Dart suits your program, write Dart. Flutter is excellent and it is not in competition with this — the framework here is Flutter's, and we would rather it were understood as Flutter reaching a new language than as a rival.
This exists for the programs where the language is the constraint: where you need real shared-memory threads, or you live on top of native libraries, or you cannot have a VM and a garbage collector in the process, or your team simply writes Swift and has been waiting for a way to build a GUI with it that does not stop at Apple's platforms. That set turns out to be larger than it sounds — it is where systems software and applications overlap.
Our own proof is the extreme case: an entire desktop environment written on this SDK — compositor, window manager, X11 server, portals, and the apps on top of them. If the framework can carry that, an ordinary application is not going to trouble it.
Where it runs today
We would rather be believed than impressive, so this is the real state rather than the roadmap.
FlutterMacOSBridge
target against the engine's macOS embedder, but nothing verifies them yet —
that needs a machine and CI, and until it has both we will not claim it
works. It is the next platform we intend to finish.
Known gaps
The framework does not yet answer System.requestAppExit, so a host
closes its window directly rather than letting the framework veto. The
flutter/keyevent channel replies are not yet the JSON the GTK
keyboard handler expects, which logs a warning on focus changes. And on Ubuntu
26.04 a toolchain-versus-distro mismatch means consumers need two extra
compiler flags — the workaround is in the repo, and it disappears when
swift.org ships a native 26.04 toolchain.
Everything here was written with it
The most useful documentation for a framework is a program that uses it seriously, and this one comes with a dozen. The Starling desktop's first-party apps are ordinary SDK consumers — each a SwiftPM package that depends on the framework exactly the way your app will, with nothing privileged about them:
- Calculator — ~480 lines. The one to read first.
- Text Editor and Files — text editing, list virtualisation, real file I/O.
- Terminal — a pty, an escape-sequence parser, and a custom-painted grid.
- Settings — ~2,300 lines, nine panes, the largest of them.
- The SDK's own examples — the Flutter counter, a todo app, and a port of the kalender package with day, week and month views.
That is the part worth taking seriously if you are weighing whether the
framework is real. There is no separate privileged API that the desktop uses
and applications do not. The shell composites its windows into a widget tree,
lays them out with the same constraint model as a Column, and
rebuilds through setState. When we hit a limit, we fix it in the
framework — which is why the framework is the part that gets better.
Build something with it
One download, four files, one swift build — no engine checkout,
no toolchain beyond Swift. The guide walks the whole loop on an ordinary
Linux session.