shipping · running below this line

SwiftUI semantics,
drawn in terminal cells.

A Swift package that lowers SwiftUI-shaped views through a strict seven-phase pipeline. One App declaration drives three runtimes: SwiftTUICLI, SwiftTUIWASI, and embedded SwiftUI / Web hosts.

resolve measure place semantics draw raster commit
Package.swift · swift-tools-version: 6.3
.package(url: "https://github.com/GoodHatsLLC/SwiftTUI", branch: "main")
.product(name: "SwiftTUI", package: "SwiftTUI")
.product(name: "SwiftTUICLI", package: "SwiftTUI")
  • requiresSwift 6.3 · macOS 15 · iOS 18 · linux
  • deviationrepo-owned @Bindable, tree-forward lists
  • deferredNavigationStack, popover surfaces
/01Frame pipeline

Seven phases. Strict order. No collapsing.

Layout is recursive parent-child negotiation, not a global solver. State drives rendering, not the reverse. The phase order is enforced by DefaultRenderer, FrameArtifacts, and the regression suites.

  1. 01 resolve

    Authored views collapse into the resolved tree.

    Resolver · ResolvedNode
  2. 02 measure

    Parents propose sizes, children choose.

    ProposedSize · ChosenSize
  3. 03 place

    Recursive layout assigns integer-cell rects.

    PlacementPlan
  4. 04 semantics

    Focus chain, accessibility, identity rolls up.

    SemanticTree
  5. 05 draw

    Per-cell glyphs and styles emit as commands.

    DrawCommand
  6. 06 raster

    Commands rasterize into a capability-aware surface.

    RasterSurface
  7. 07 commit

    Diff against last frame and stream ANSI/OSC.

    CommitPlan
resolve · measure · place · semantics · draw · raster · commit ·
/02Three runtimes, one App

Authored once. Hosted anywhere a Swift compiler reaches.

The same @main struct App declaration flows into terminal-native binaries, WASI browsers via SharedArrayBuffer-backed stdin, and host-managed embeddings inside SwiftUI windows or DOM elements.

native Runners/SwiftTUICLI

Terminal-native

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

import SwiftTUICLI

@main struct DemoApp: App {
  var body: some Scene {
    WindowGroup("Deploy Dashboard") {
      BuildSummary()
    }
  }
}
  • shipped · macOS 15+ · linux · iOS 18+
  • android · in tree, not yet certified
wasi Runners/SwiftTUIWASI

Browser, via WASI

Build the same target to wasm32-wasi. The WASIRunner emits a scene manifest the host loads through GUI/WebHost — 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 GUI/SwiftUIHost

Inside SwiftUI

Retain a HostedSceneSession in your SwiftUI view. Theme tokens swap at runtime; the authored TUI never sees the host.

SwiftUIHost(scene: deployScene)
  .terminalTheme(.solarizedDark)
  .frame(minWidth: 560, minHeight: 320)
embed GUI/WebHost

Inside any DOM element

Drop a .terminal-shell div into a Bun, Astro, Remix, or Next page. The host owns chrome — the framework owns the cells.

<div class="terminal-shell"
     data-scene="gallery"></div>
<script type="module" src="/host.js"></script>
/03Public surface

Implementing a useful subset of SwiftUI, deliberately.

Layout primitives first. State and environment second. Runtime correctness third. Broader orchestration only after the foundation is firm. Terminal-only chrome that would bend the public authoring story does not belong in core.

AUTHORING 20 entries

Layout & containers

  • VStack · HStack · ZStack stacked layout with spacing & alignment
  • LazyVStack · LazyHStack viewport-lazy placement, single-ForEach full-lazy rows
  • ScrollView · List · OutlineGroup tree-forward collection presentation
  • Table · Section · GroupBox
  • ViewThatFits · GeometryReader
  • Layout (custom) public protocol for custom layout participation

Controls & primitives

  • Text · TextFigure proposal-aware FIGlet rendering with embedded fonts
  • Button · Link · Label OSC 8 hyperlink emission when supported
  • Toggle · Stepper · Slider
  • TextField · TextEditor · SecureField
  • Picker · Menu · DisclosureGroup
  • ProgressView · Spacer · Divider · ControlGroup
  • Image PNG · baseline JPEG · GIF (first frame), Kitty / Sixel / ANSI fallback

Presentation & workflow

  • alert · confirmationDialog terminal-native modal surfaces
  • sheet · toast
  • Panel · .keyCommand · .paletteCommand ActionScopes & commands
  • .toolbar · .toolbarItem shipped via ACTION_SCOPES_AND_COMMANDS

Environment · observation · focus

  • @State · @Binding · @Bindable repo-owned @Bindable on the @State invalidation path
  • @FocusState · FocusedValues · @FocusedValue · @FocusedBinding
  • PreferenceKey · anchor preferences subtree readers, OpenLinkAction, .task(...)
RUNTIME · SCENES · CHARTS 17 entries

Runtime surface

  • DefaultRenderer one-shot rendering & main-actor pipeline inspection
  • RunLoop interactive terminal sessions
  • TerminalHost · TerminalRenderStyle capability-aware presentation, theme swap
  • Keyboard · mouse · signals Unix signal handling and runtime scheduling
  • Identity-driven lifecycle diffs appear · disappear · task start · cancel

Scenes

  • App · Scene · WindowGroup · WindowIdentifier @MainActor scene declarations
  • SceneDescriptor · SceneManifest for tooling and retained hosted sessions
  • HostedSceneSession host-package retention point
  • TabView terminal-native shell composition
  • Pty-backed secondary scenes Unix-domain-socket discovery, lazy unattached rendering

Charts

  • BarChart · ColumnChart · ComparisonChart
  • Sparkline · Timeline · HeatStrip
  • ThresholdGauge · BulletChart · Meter · Legend
  • StackedBarChart

Deferred · by design

  • NavigationStack deferred
  • popover-style presentation beyond sheet deferred
  • richer accessibility / AT modeling deferred
/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 tests, snapshots, and CI fixtures.

BuildSummary.swift 38 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)
)
stdout capability: previewUnicode
 Deploy Queue
 ────────────────────────────────────
 Release
 █████████████████████████░░░░░░░░ 18 / 24
 Window                       staging
 Owner                          infra

 _                                     
resolved — 14 nodes diff: 6 cells commit: 0.31 ms