Tools: SwiftUI for Linux and Windows (2026)

Tools: SwiftUI for Linux and Windows (2026)

Write SwiftUI, Run on Linux and Windows

What is SwiftOpenUI?

Who is this for?

What you get today

Setting up your development environment

Linux (Ubuntu / Debian / Fedora)

Windows

Your first cross-platform app

Building something real

Settings rows

Dashboard cards

Sidebar / detail split

Status bar

More showcase apps

Calculator (Windows)

ColorMixer (Linux)

SimplePaint (Windows)

How we verify layout parity

Running the examples

What's next You love SwiftUI's declarative model. VStack, @State, .padding() — it's the best way to build UI in Swift. But it locks you to Apple platforms. What if the same code rendered natively on Linux and Windows? Same Swift source file. Native GTK4 on Linux. Native Win32 + Direct2D on Windows. Real SwiftUI on macOS. No Electron, no webview wrappers. SwiftOpenUI is a re-implementation of the SwiftUI API surface that compiles and renders on non-Apple platforms. You write standard SwiftUI code — Text, VStack, @State, .frame(), .padding() — and it runs on Linux (via GTK4) and Windows (via Win32/Direct2D). The key design: on macOS, your code imports real SwiftUI. This keeps you honest — if it compiles with Apple's SwiftUI, it compiles with SwiftOpenUI. The view code is identical across all platforms. Only the one-line entry point differs. It's not a new framework to learn. If you know SwiftUI, you already know SwiftOpenUI. The architecture is simple: The core library has zero platform imports. All GTK/Win32 code lives in separate backend modules. Views, state management, layout, modifiers, and environment — all shared. SwiftOpenUI covers a broad surface of the SwiftUI API: 44 views including Text, Button, TextField, Toggle, Slider, Image, Picker, List, ScrollView, NavigationStack, Canvas, Grid, shapes (Circle, Rectangle, RoundedRectangle, Capsule, Ellipse), LinearGradient, RadialGradient, and more. 43+ modifiers including .padding(), .frame(), .foregroundColor(), .background(), .font(), .sheet(), .alert(), .toolbar(), .searchable(), .contextMenu(), .overlay(), .clipShape(), .animation(), withAnimation(), .help(), .resizable(), and more. Full state management: @State, @Binding, @observable, @Bindable, @ObservedObject, @StateObject, @EnvironmentObject, @Published, @Environment, @FocusState, @FocusedValue. App structure: WindowGroup, Window, Commands with native menu bar on both GTK4 and Win32. Here's a concrete example. This SwiftUI view compiles and renders identically on all three platforms: Nothing platform-specific. Standard SwiftUI. You probably already have everything you need. On macOS, examples use real SwiftUI — this is your reference implementation. Install the Swift toolchain and GTK4 development libraries: Install the Swift toolchain and Visual Studio Build Tools: On all three platforms, HelloWorld shows centered text in a native window — same code, same result. Here's the complete HelloWorld source. One file, runs everywhere: This is the boilerplate. Every example in the project follows this pattern. Your actual view code is pure SwiftUI. HelloWorld proves the toolchain works. Let's look at something that exercises real layout patterns — the kind of composition that breaks cross-platform frameworks. The LayoutStress showcase app has five sections, each targeting a different hard case: The classic label/spacer/value pattern. Full-width rows with right-aligned values and thin dividers: This tests: HStack with Spacer, VStack(spacing: 0), .lineLimit(1) truncation, sub-pixel divider heights (0.5pt), and full-width expansion via .frame(maxWidth: .infinity). Equal-width flex distribution — two cards side by side, then three: This tests: .frame(maxWidth: .infinity) inside HStack children (each card must claim an equal share of the available width), nested VStack inside HStack, and background color rendering. Fixed-width sidebar with flexible detail pane — the pattern every document-based app uses: This tests: fixed-width alongside flexible-width, Spacer() pushing content to top, .frame(width:) constraints, and Color as a 1pt divider. The multi-section status bar pattern — this is the exact composition that found real layout bugs during development: This tests: mixed fixed and flexible sections in HStack, Color as sized dividers, .frame(maxWidth: .infinity) claiming the center, and fixed-height constraint on the entire bar. The full LayoutStress source is a single main.swift — 300 lines, all standard SwiftUI patterns. A fully functional calculator built with Grid and GridRow — the SwiftUI grid layout system. Each button is a styled view with .background(), .foregroundColor(), and tap gesture handling. The display uses @State to track input, operations, and evaluation. The entire app is one main.swift with ZStack for the dark background and VStack(spacing: 0) for the display + button grid composition. A color picker with RGB sliders, color swatches, and complementary/triadic harmony display. Uses @ObservedObject for the color model, Slider for each channel, ForEach for the swatch grid, and .background(Color(...)) for live color previews. Demonstrates how SwiftUI's reactive state model works identically on every platform — drag a slider on Linux and the color updates the same way it does on macOS. A drawing app with pencil, eraser, line, rectangle, and ellipse tools. Built on Canvas and Path for rendering, .onDrag() for gesture input, and @State arrays for undo/redo history. Shows that SwiftOpenUI handles not just form layouts but also freeform drawing and real-time gesture tracking across platforms. Cross-platform layout is hard. A view that looks right on macOS can break subtly on GTK4 or Win32 — wrong spacing, misaligned content, collapsed sections. These bugs are invisible to unit tests and only surface in real app layouts. SwiftOpenUI has an automated layout parity test suite that catches these: macOS captures the reference. Each test scenario renders a real SwiftUI view, walks the view tree, and saves the positions and sizes as a JSON fixture. Each platform renders the same view. GTK4 captures GTK widget positions. Win32 captures HWND positions. Same JSON format. Automated comparison. A shared comparison engine matches leaf nodes (the actual visible content), normalizes coordinates, and reports differences. Structural layout bugs (wrong position, wrong spacing) fail the test. Font-metric differences (expected — different fonts on each platform) are reported as informational. 50 test scenarios covering stacks, frames, padding, spacers, alignment, and the composition patterns that real apps use. Current parity: GTK4 passes 49/50 scenarios. Win32 passes 37/50 (remaining are font-metric cascades, not layout bugs). This is measurable, automated layout parity — not "it looks about right." SwiftOpenUI ships with 6 showcase apps and 11 parity test apps: All 17 examples compile and run on macOS, Linux, and Windows from the same source. Active development is focused on: The project is MIT licensed and open source. If you're interested in cross-platform Swift UI: Pick a view from the parity matrix, implement the backend rendering, and send a PR. Every contribution moves the framework closer to full SwiftUI coverage. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ SwiftOpenUI Core (platform-independent) | +-- BackendGTK4 (Linux) -> GTK4 native widgets +-- BackendWin32 (Windows) -> HWND + Direct2D rendering SwiftOpenUI Core (platform-independent) | +-- BackendGTK4 (Linux) -> GTK4 native widgets +-- BackendWin32 (Windows) -> HWND + Direct2D rendering SwiftOpenUI Core (platform-independent) | +-- BackendGTK4 (Linux) -> GTK4 native widgets +-- BackendWin32 (Windows) -> HWND + Direct2D rendering struct SettingsRow: View { let label: String let value: String var body: some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } } struct SettingsRow: View { let label: String let value: String var body: some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } } struct SettingsRow: View { let label: String let value: String var body: some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } } xcode-select ---weight: 500;">install -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld xcode-select ---weight: 500;">install -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld xcode-select ---weight: 500;">install -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld # Install Swift via swiftly (official Swift version manager) -weight: 500;">curl -L https://swiftlang.github.io/swiftly/swiftly--weight: 500;">install.sh | bash source ~/.swiftly/env.sh # Install GTK4 development libraries -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install libgtk-4-dev # Ubuntu / Debian # -weight: 600;">sudo -weight: 500;">dnf -weight: 500;">install gtk4-devel # Fedora # Clone and run -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld # Install Swift via swiftly (official Swift version manager) -weight: 500;">curl -L https://swiftlang.github.io/swiftly/swiftly--weight: 500;">install.sh | bash source ~/.swiftly/env.sh # Install GTK4 development libraries -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install libgtk-4-dev # Ubuntu / Debian # -weight: 600;">sudo -weight: 500;">dnf -weight: 500;">install gtk4-devel # Fedora # Clone and run -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld # Install Swift via swiftly (official Swift version manager) -weight: 500;">curl -L https://swiftlang.github.io/swiftly/swiftly--weight: 500;">install.sh | bash source ~/.swiftly/env.sh # Install GTK4 development libraries -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install libgtk-4-dev # Ubuntu / Debian # -weight: 600;">sudo -weight: 500;">dnf -weight: 500;">install gtk4-devel # Fedora # Clone and run -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld -weight: 500;">git clone https://github.com/codelynx/SwiftOpenUI.-weight: 500;">git cd SwiftOpenUI swift run HelloWorld #if os(macOS) import SwiftUI import MacExampleSupport #else import SwiftOpenUI #if canImport(BackendGTK4) import BackendGTK4 #endif #if canImport(BackendWin32) import BackendWin32 #endif #endif struct HelloWorldApp: App { var body: some Scene { WindowGroup("Hello World") { Text("Hello, SwiftOpenUI!") .padding() } } } #if os(macOS) MacAppLauncher.run(HelloWorldApp.self) #elseif canImport(BackendGTK4) GTK4Backend().run(HelloWorldApp.self) #elseif canImport(BackendWin32) Win32Backend().run(HelloWorldApp.self) #endif #if os(macOS) import SwiftUI import MacExampleSupport #else import SwiftOpenUI #if canImport(BackendGTK4) import BackendGTK4 #endif #if canImport(BackendWin32) import BackendWin32 #endif #endif struct HelloWorldApp: App { var body: some Scene { WindowGroup("Hello World") { Text("Hello, SwiftOpenUI!") .padding() } } } #if os(macOS) MacAppLauncher.run(HelloWorldApp.self) #elseif canImport(BackendGTK4) GTK4Backend().run(HelloWorldApp.self) #elseif canImport(BackendWin32) Win32Backend().run(HelloWorldApp.self) #endif #if os(macOS) import SwiftUI import MacExampleSupport #else import SwiftOpenUI #if canImport(BackendGTK4) import BackendGTK4 #endif #if canImport(BackendWin32) import BackendWin32 #endif #endif struct HelloWorldApp: App { var body: some Scene { WindowGroup("Hello World") { Text("Hello, SwiftOpenUI!") .padding() } } } #if os(macOS) MacAppLauncher.run(HelloWorldApp.self) #elseif canImport(BackendGTK4) GTK4Backend().run(HelloWorldApp.self) #elseif canImport(BackendWin32) Win32Backend().run(HelloWorldApp.self) #endif VStack(alignment: .leading, spacing: 0) { Text("GENERAL") .font(.caption) .foregroundColor(.gray) .padding(.horizontal, 16) .padding(.vertical, 8) settingsRow("Username", value: "kaz.yoshikawa") settingsDivider() settingsRow("Email", value: "[email protected]") settingsDivider() settingsRow("Language", value: "English") } func settingsRow(_ label: String, value: String) -> some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } VStack(alignment: .leading, spacing: 0) { Text("GENERAL") .font(.caption) .foregroundColor(.gray) .padding(.horizontal, 16) .padding(.vertical, 8) settingsRow("Username", value: "kaz.yoshikawa") settingsDivider() settingsRow("Email", value: "[email protected]") settingsDivider() settingsRow("Language", value: "English") } func settingsRow(_ label: String, value: String) -> some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } VStack(alignment: .leading, spacing: 0) { Text("GENERAL") .font(.caption) .foregroundColor(.gray) .padding(.horizontal, 16) .padding(.vertical, 8) settingsRow("Username", value: "kaz.yoshikawa") settingsDivider() settingsRow("Email", value: "[email protected]") settingsDivider() settingsRow("Language", value: "English") } func settingsRow(_ label: String, value: String) -> some View { HStack { Text(label) Spacer() Text(value) .foregroundColor(.gray) .lineLimit(1) } .padding(.horizontal, 16) .padding(.vertical, 10) } HStack(spacing: 12) { card(title: "CPU", value: "45%", color: .orange) card(title: "Memory", value: "2.1 GB", color: .red) card(title: "Disk", value: "128 GB", color: .purple) } func card(title: String, value: String, color: Color) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title).font(.caption).foregroundColor(.gray) Text(value).font(.title).foregroundColor(color) } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(Color(red: 0.15, green: 0.15, blue: 0.15)) } HStack(spacing: 12) { card(title: "CPU", value: "45%", color: .orange) card(title: "Memory", value: "2.1 GB", color: .red) card(title: "Disk", value: "128 GB", color: .purple) } func card(title: String, value: String, color: Color) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title).font(.caption).foregroundColor(.gray) Text(value).font(.title).foregroundColor(color) } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(Color(red: 0.15, green: 0.15, blue: 0.15)) } HStack(spacing: 12) { card(title: "CPU", value: "45%", color: .orange) card(title: "Memory", value: "2.1 GB", color: .red) card(title: "Disk", value: "128 GB", color: .purple) } func card(title: String, value: String, color: Color) -> some View { VStack(alignment: .leading, spacing: 8) { Text(title).font(.caption).foregroundColor(.gray) Text(value).font(.title).foregroundColor(color) } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(Color(red: 0.15, green: 0.15, blue: 0.15)) } HStack(spacing: 0) { VStack(alignment: .leading, spacing: 0) { sidebarItem("Inbox", count: "12", selected: true) sidebarItem("Sent", count: "3", selected: false) sidebarItem("Drafts", count: "1", selected: false) sidebarItem("Archive", count: "847", selected: false) Spacer() } .frame(width: 140) Color.gray.frame(width: 1) // divider VStack(alignment: .leading, spacing: 12) { // detail content } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) } .frame(height: 250) HStack(spacing: 0) { VStack(alignment: .leading, spacing: 0) { sidebarItem("Inbox", count: "12", selected: true) sidebarItem("Sent", count: "3", selected: false) sidebarItem("Drafts", count: "1", selected: false) sidebarItem("Archive", count: "847", selected: false) Spacer() } .frame(width: 140) Color.gray.frame(width: 1) // divider VStack(alignment: .leading, spacing: 12) { // detail content } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) } .frame(height: 250) HStack(spacing: 0) { VStack(alignment: .leading, spacing: 0) { sidebarItem("Inbox", count: "12", selected: true) sidebarItem("Sent", count: "3", selected: false) sidebarItem("Drafts", count: "1", selected: false) sidebarItem("Archive", count: "847", selected: false) Spacer() } .frame(width: 140) Color.gray.frame(width: 1) // divider VStack(alignment: .leading, spacing: 12) { // detail content } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) } .frame(height: 250) HStack(spacing: 0) { HStack(spacing: 6) { Color.green.frame(width: 8, height: 8) Text("Connected").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) Color.gray.frame(width: 1, height: 16) // divider Text("3 files synced") .font(.caption) .foregroundColor(.gray) .frame(maxWidth: .infinity) // fills center Color.gray.frame(width: 1, height: 16) // divider HStack(spacing: 12) { Text("45%").font(.caption).foregroundColor(.gray) Text("2.1 MB/s").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) } .frame(height: 28) HStack(spacing: 0) { HStack(spacing: 6) { Color.green.frame(width: 8, height: 8) Text("Connected").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) Color.gray.frame(width: 1, height: 16) // divider Text("3 files synced") .font(.caption) .foregroundColor(.gray) .frame(maxWidth: .infinity) // fills center Color.gray.frame(width: 1, height: 16) // divider HStack(spacing: 12) { Text("45%").font(.caption).foregroundColor(.gray) Text("2.1 MB/s").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) } .frame(height: 28) HStack(spacing: 0) { HStack(spacing: 6) { Color.green.frame(width: 8, height: 8) Text("Connected").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) Color.gray.frame(width: 1, height: 16) // divider Text("3 files synced") .font(.caption) .foregroundColor(.gray) .frame(maxWidth: .infinity) // fills center Color.gray.frame(width: 1, height: 16) // divider HStack(spacing: 12) { Text("45%").font(.caption).foregroundColor(.gray) Text("2.1 MB/s").font(.caption).foregroundColor(.gray) } .padding(.horizontal, 12) } .frame(height: 28) # Showcase swift run HelloWorld # Minimal app swift run Stopwatch # Timer with -weight: 500;">start/-weight: 500;">stop/lap swift run ColorMixer # Color picker with sliders and harmony swift run Calculator # Grid-based calculator swift run SimplePaint # Drawing app with tools and undo swift run LayoutStress # Advanced layout composition stress test # Parity (one per feature category) swift run ParityViewsBasic swift run ParityViewsLayout swift run ParityModifiers swift run ParityStateData swift run ParityNavigation swift run ParityEnvironment swift run ParityGestures swift run ParityAnimation swift run ParityFocus swift run ParityAppStructure swift run ParityViewsContainers # Showcase swift run HelloWorld # Minimal app swift run Stopwatch # Timer with -weight: 500;">start/-weight: 500;">stop/lap swift run ColorMixer # Color picker with sliders and harmony swift run Calculator # Grid-based calculator swift run SimplePaint # Drawing app with tools and undo swift run LayoutStress # Advanced layout composition stress test # Parity (one per feature category) swift run ParityViewsBasic swift run ParityViewsLayout swift run ParityModifiers swift run ParityStateData swift run ParityNavigation swift run ParityEnvironment swift run ParityGestures swift run ParityAnimation swift run ParityFocus swift run ParityAppStructure swift run ParityViewsContainers # Showcase swift run HelloWorld # Minimal app swift run Stopwatch # Timer with -weight: 500;">start/-weight: 500;">stop/lap swift run ColorMixer # Color picker with sliders and harmony swift run Calculator # Grid-based calculator swift run SimplePaint # Drawing app with tools and undo swift run LayoutStress # Advanced layout composition stress test # Parity (one per feature category) swift run ParityViewsBasic swift run ParityViewsLayout swift run ParityModifiers swift run ParityStateData swift run ParityNavigation swift run ParityEnvironment swift run ParityGestures swift run ParityAnimation swift run ParityFocus swift run ParityAppStructure swift run ParityViewsContainers - Swift developers who want to ship tools on Linux — server admin panels, developer utilities, data viewers - Swift developers who want to ship on Windows — internal tools, build monitors, configuration editors - Teams with existing SwiftUI code who want to bring it cross-platform without a rewrite - Developers who like Swift's type safety and declarative UI but need to target non-Apple platforms - Download and -weight: 500;">install the Swift toolchain for Windows (includes the compiler and SPM) - Install Visual Studio Build Tools with the C++ Desktop Development workload (needed for the linker and Windows SDK headers) - Open a Developer Command Prompt or Developer PowerShell - #if os(macOS) imports real SwiftUI — your parity check - #else imports SwiftOpenUI with whichever backend is available - The view code (HelloWorldApp, WindowGroup, Text) is identical across all three - Only the entry point differs — one line per platform - macOS captures the reference. Each test scenario renders a real SwiftUI view, walks the view tree, and saves the positions and sizes as a JSON fixture. - Each platform renders the same view. GTK4 captures GTK widget positions. Win32 captures HWND positions. Same JSON format. - Automated comparison. A shared comparison engine matches leaf nodes (the actual visible content), normalizes coordinates, and reports differences. Structural layout bugs (wrong position, wrong spacing) fail the test. Font-metric differences (expected — different fonts on each platform) are reported as informational. - 50 test scenarios covering stacks, frames, padding, spacers, alignment, and the composition patterns that real apps use. - Adaptive default spacing — macOS SwiftUI uses 0pt between adjacent Text views; SwiftOpenUI currently uses 8pt. Fixing this closes most remaining Win32 layout residuals. - Layout priority-based flex distribution — SwiftUI's priority-based algorithm for dividing space among flexible children. - Continued parity testing — expanding the 50-scenario suite as new views and modifiers are added. - GitHub: github.com/codelynx/SwiftOpenUI - Getting Started: docs/guides/getting-started.md - Feature Parity Matrix: docs/architecture/swiftui-parity-matrix.md