0.1.0 alpha · live demo below

SwiftUI semantics,
drawn in terminal cells.

A Swift package that lowers SwiftUI-shaped views through a strict seven-phase pipeline — no global solver, no virtual DOM, no curses. The same App and Scene declarations can run in a terminal executable, through WASI browser deployment, through a native WebHost launch, or inside a host-managed SwiftUI surface.

Package.swift · swift-tools-version: 6.3 · quickstart →
// add to dependencies
.package(
  url: "https://github.com/SwiftTUI/swift-tui",
  .upToNextMinor(from: "0.1.0")
)

// add to your executable target
.product(name: "SwiftTUI", package: "swift-tui")
  • requiresSwift 6.3 · macOS 15+ / iOS 18+ package platforms · Linux/WASI paths
  • stability0.1.0 alpha · source-breaking changes may land before 1.0
  • runs interminal · WASI browser package · localhost WebHost · SwiftUI host
/00Why this exists

Built for engineers who want SwiftUI's authoring model in places SwiftUI doesn't ship.

Most TUI frameworks invent a new authoring model for the terminal. SwiftTUI keeps the SwiftUI authoring model and reinterprets the substrate. Same View, @State, @FocusState, PreferenceKey, and Layout protocol you already know — drawn as integer-cell glyphs instead of CoreAnimation layers.

  1. 01

    SwiftUI semantics, not a clone

    Recursive parent-child layout. Graph-scoped state drives rendering, not the reverse. Modifier order matters because modifiers participate in layout and semantics. The terminal target does not bend the authoring story.

    @MainActor · View · @State · Layout
  2. 02

    One app. Several host products.

    The same App and Scene declarations can feed a terminal executable, a WASI browser deployment, a localhost WebHost launch, or a host-managed SwiftUI surface. The framework owns the cells; the host owns the chrome.

    SwiftTUI · SwiftTUIWASI · SwiftTUIWebHostCLI · SwiftUIHost
  3. 03

    Strict, inspectable pipeline

    Seven phases — resolve, measure, place, semantics, draw, raster, commit — stay visible through DefaultRenderer and FrameArtifacts. Inspect a frame when you need to understand layout, routing, or final cells.

    DefaultRenderer · FrameArtifacts
  4. 04

    Capability-aware, not lowest-common-denominator

    ANSI, sanitized OSC 8 hyperlinks, Kitty graphics, Sixel, truecolor, and mouse reporting are negotiated per session against a live capability profile. PNG and baseline JPEG decode in pure Swift; GIF playback ships in the AnimatedImage peer product.

    TerminalCapabilityProfile
  5. 05

    Swift 6 library, normal app code

    SwiftTUI builds with Swift 6 language mode, explicit @MainActor authoring APIs, and Sendable frame artifacts. Your app can consume the package without copying those build settings.

    Swift 6 · @MainActor View.body · SwiftPM package
  6. 06

    No dependencies on a terminal emulator

    The browser path does not embed xterm.js or a DOM terminal. WebHost mounts a scene runtime directly. Tests render frames as integer-cell rasters without spinning up a TTY.

    WebHost · RasterSurface · no xterm.js
/01Frame pipeline

Seven phases. Strict order. No collapsing.

Layout is recursive parent-child negotiation, not a global solver. Graph-scoped state drives rendering, not the reverse. The runtime keeps each phase explicit, and DefaultRenderer exposes FrameArtifacts when you want to inspect or snapshot a frame.

  1. 01 resolve

    Authored views collapse into the resolved tree.

    Resolver · ResolvedNode
  2. 02 measure

    Parents propose sizes, children choose.

    ProposedSize · MeasuredNode
  3. 03 place

    Recursive layout assigns integer-cell rects.

    PlacedNode
  4. 04 semantics

    Focus, accessibility, actions, selection, and scroll routes are extracted.

    SemanticSnapshot
  5. 05 draw

    Per-cell glyphs and styles emit as commands.

    DrawNode
  6. 06 raster

    Draw commands become a styled cell grid.

    RasterSurface
  7. 07 commit

    Lifecycle and handler work are packaged for the runtime.

    CommitPlan
resolve · measure · place · semantics · draw · raster · commit ·
Read the full pipeline guide →
/02Launch and host products

Authored once. Run through the host you choose.

The same App and Scene declarations flow into terminal-native binaries, browser deployments built with SwiftTUIWASI, localhost browser sessions through SwiftTUIWebHost, and retained native scenes through SwiftUIHost. The framework owns the cells; each host owns its chrome.

native SwiftTUI · SwiftTUICLI

Terminal-native

RunLoop over the alternate screen. ANSI / sanitized OSC 8 hyperlinks / Kitty / Sixel negotiated against the live capability profile. Unix signal handling, mouse reporting, and pty-backed secondary scenes ship by default.

import SwiftTUI

@main struct DemoApp: App {
  var body: some Scene {
    WindowGroup("Deploy Dashboard") {
      BuildSummary()
    }
  }
}
  • terminal convenience product ships from the root package
  • WebHost support is compile-time opt-in
wasi SwiftTUIWASI · Platforms/Web

Browser, via WASI

Build the same target to wasm32-wasi. The WASIRunner emits a scene manifest the host loads through Platforms/Web — no terminal emulator, no xterm.js, no postMessage shim.

// build
swift build --swift-sdk swift-6.3.1-RELEASE_wasm \
  --target WebExampleApp

// host
await createWebHostApp({
  manifestUrl: "./scene-manifest.json",
  sceneRuntimeFactory: createWasmSceneRuntimeFactory(...)
})
  • requires COOP: same-origin · COEP: require-corp
embed SwiftUIHost

Inside SwiftUI

Retain SwiftTUI scenes inside a native SwiftUI app shell. Theme tokens swap at runtime; the authored TUI does not branch on the host style.

let state = try SwiftUIHostAppState(
  app: DemoApp(),
  style: .default
)
SwiftUIHostAppView(state: state)
embed SwiftTUIWebHost

Localhost browser host

Native binaries can opt into --web routing. The runner serves the bundled browser runtime over localhost and carries frames through the shared web-surface protocol.

import SwiftTUIWebHostCLI

@main struct DemoApp: App {
  // normal launch: terminal; --web: browser host
}
/03Public surface

A deliberate subset of SwiftUI.

Layout, state, focus, and presentation — plus the runtime and scene types that drive them, and a peer charts product. Terminal-only chrome that would bend the public authoring story stays out of core. The full per-symbol reference lives in the DocC catalogs.

AUTHORING

Layout & containers

  • VStack · HStack · ZStack · Spacer · Divider
  • LazyVStack · LazyHStack · ScrollView
    viewport-lazy placement
  • List · OutlineGroup · Table · Section · GroupBox
  • Layout · AnyLayout · ViewThatFits · GeometryReader
    public protocol for custom layout

Controls & primitives

  • Text · TextFigure · Label · Image
    FIGlet figures · PNG / baseline JPEG images
  • Button · Link · Toggle · Slider · Stepper
    sanitized OSC 8 hyperlinks when supported
  • TextField · TextEditor · SecureField · Picker · Menu
  • ProgressView · DisclosureGroup · ControlGroup

Presentation & workflow

  • alert · confirmationDialog · sheet · popover · toast
    terminal-native modal & non-modal surfaces
  • paletteSheet · Panel · .keyCommand · .paletteCommand
    action scopes and commands
  • .toolbar · .toolbarItem

State · observation · focus

  • @State · @Binding · @Bindable
    graph-scoped invalidation and observation
  • @FocusState · @FocusedValue · @FocusedBinding
  • @Environment · PreferenceKey · anchor preferences
RUNTIME · SCENES · CHARTS

Runtime & scenes

  • DefaultRenderer · FrameArtifacts
    snapshot rendering and frame inspection
  • RunLoop · TerminalHost · TerminalCapabilityProfile
    capability-aware interactive sessions
  • App · Scene · WindowGroup · TabView
    @MainActor scene declarations
  • SceneManifest · HostedSceneSession
    retained hosted sessions for host packages
  • NavigationStack destinations
    binding-driven destination presentation
  • Pty-backed secondary scenes
    Unix-domain-socket discovery

Charts

  • BarChart · ColumnChart · StackedBarChart · ComparisonChart
  • LineChart · Sparkline · Timeline · HeatStrip · CalendarHeatmap
  • BulletChart · ThresholdGauge · Meter · Legend
/04Authoring → render

One View. Resolve, place, and rasterize — into cells.

At the lowest public runtime level, you can resolve and render any View into terminal text. Same code path your App takes inside RunLoop, exposed for previews, unit tests, documentation examples, and debug output without opening an interactive terminal session.

BuildSummary.swift 40 cells × 8 rows · proposal
import SwiftTUI

struct BuildSummary: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 0) {
      Text("Deploy Queue").bold()
      Divider()
      ProgressView("Release", value: 18, total: 24)
      LabeledContent("Window", value: "staging")
      LabeledContent("Owner",  value: "infra")
    }
    .padding(.init(horizontal: 1, vertical: 0))
  }
}

let renderer = DefaultRenderer()
let frame = renderer.render(
  BuildSummary(),
  proposal: .init(width: 40, height: 8)
)
TerminalSurfaceRenderer · previewUnicode illustrative
 Deploy Queue
 ────────────────────────────────────
 Release
 █████████████████████████░░░░░░░░ 18 / 24
 Window                       staging
 Owner                          infra

 _                                     
renderer.render(_:proposal:) frame → FrameArtifacts surface → RasterSurface
/05Quickstart

Two files. One swift run.

No code generators, no build scripts, no binaries to download — just SwiftPM. Copy the two files below into a new directory and run it.

prereq A Swift 6.3 toolchain on PATH — install it however you already manage Swift.

  1. 01
    create the package A Package.swift with one dependency on SwiftTUI and one executable target.
  2. 02
    author an @main App A struct conforming to App, with a Scene built from the SwiftUI-shaped primitives you already know.
  3. 03
    swift run Standard SwiftPM. Your app takes the alternate screen until you exit.
~/Projects/HelloTUI
# build & run — your app owns the terminal until you exit
$ swift run HelloTUI
 alternate screen opens
 resolve · measure · place · semantics · draw · raster · commit
 press Ctrl-D to exit — your shell is restored
Package.swift swift-tools-version: 6.3
// swift-tools-version: 6.3
import PackageDescription

let package = Package(
  name: "HelloTUI",
  dependencies: [
    .package(
      url: "https://github.com/SwiftTUI/swift-tui",
      .upToNextMinor(from: "0.1.0")
    ),
  ],
  targets: [
    .executableTarget(
      name: "HelloTUI",
      dependencies: [
        .product(name: "SwiftTUI", package: "swift-tui"),
      ]
    ),
  ]
)
Sources/HelloTUI/HelloTUI.swift @main · terminal-native
import SwiftTUI

@main struct HelloTUI: App {
  var body: some Scene {
    WindowGroup("Hello") {
      VStack(alignment: .leading, spacing: 1) {
        Text("Hello, terminal.").bold()
        Text("Press Ctrl-D to quit.")
          .foregroundStyle(.muted)
      }
      .padding(1)
    }
  }
}

Targeting the browser? Pass --swift-sdk for a wasm SDK on the same swift build — the authoring code is unchanged. Embedding in a host app? Retain a HostedSceneSession through SwiftUIHost, or add SwiftTUIWebHostCLI for a localhost browser launch. Same App body, every target.