Build Swift Terminal User Interfaces

Views, State, Observation, Gestures, Animations—

SwiftUI semantics, drawn in terminal cells. One native binary for macOS, Linux, and Windows.

SourceThe counter, minus its ripple animation
Full source
import SwiftTUI

struct CounterView: View {
  @State private var count = 0

  var body: some View {
    VStack(spacing: 1) {
      TextFigure("\(count)", font: .future)
        .frame(minWidth: 14, alignment: .center)
      Button("Increment") { count += 1 }
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
  }
}

@main
struct CounterApp: App {
  var body: some Scene {
    WindowGroup("Counter") { CounterView() }
  }
}
Live demoThe counter, compiled to WebAssembly
Open separately

A real SwiftTUI app, running live in your browser.

Real SwiftTUI viewStatic WASI bundleCanvas with one-way semantic accessibility tree

The same counter in your own terminal: one clone, one command.

Run it locally

Get started

A terminal and a toolchain. That's it.

SwiftTUI apps are plain SwiftPM packages. Any Swift 6.3+ toolchain builds and runs them from the command line on macOS, Linux, or Windows: no Xcode project, no simulator, no app store.

Run the counterswift-tui-counter-demo
Open repo
# macOS, Linux, or Windows — any Swift 6.3+ toolchain
git clone https://github.com/SwiftTUI/swift-tui-counter-demo.git
cd swift-tui-counter-demo
swift run --package-path counter counter

The counter from the demo above, drawn in your own terminal. Space or a click increments; Ctrl-C quits and restores your shell.

Start your own app

// Package.swift
.package(
  url: "https://github.com/SwiftTUI/swift-tui",
  .upToNextMinor(from: "0.9.11")
),
// in your executable target:
.product(name: "SwiftTUI", package: "swift-tui")

Paste the counter above into main.swift and swift run. Then the first-hour guides.

Why SwiftTUI

A proven UI model, pointed at the terminal.

Most TUI toolkits offer a low-level draw loop or a widget set with its own state story. SwiftTUI takes the declarative model SwiftUI has proven at platform scale and aims it at terminal cells: you declare views; the framework owns layout, focus, redraw, and the terminal.

  1. 01

    State in, screen out

    Change a value and the runtime recomputes layout and rewrites exactly the cells that changed. No draw loop, no buffer diffing, no repaint bookkeeping.

    View · @State · @Observable · body
  2. 02

    Real components, real focus

    Buttons, text fields, pickers, sliders, lists, tables, and scroll views, with focus traversal, keyboard chords, tap · drag · hover gestures, and animation built in.

    @FocusState · TapGesture · withAnimation · List
  3. 03

    The terminal, negotiated for you

    Truecolor, Kitty and Sixel images, OSC 8 links, and mouse reporting are probed per session and degrade gracefully, so one binary is right in kitty, over SSH, or in CI. --accessible, --reduce-motion, --no-color, and --ascii come free.

    TerminalCapabilityProfile · Kitty · Sixel · OSC 8
  4. 04

    One binary, testable without a TTY

    Swift 6 compiles your interface into a single executable with checked concurrency. A frame is a pure function of the view tree and a size, so tests compare integer-cell frames with no terminal attached.

    SwiftPM · Swift 6 · RenderOnce

How it compares

The first five columns are the day-one terminal experience; the last two are where the same source can go later.

frameworkLanguageTerminal OSDeclarative UIGesturesAnimationsWeb embedNative embed
SwiftTUISwift✓ macOS · Linux · Windows✓ SwiftUI-shaped✓ tap·drag·hover✓ withAnimation · PhaseAnimator✓ static WASI · WebHost✓ macOS · iOS · Android preview
Bubble TeaGo✓ macOS · Linux · Windows✓ Elm arch~ mouse events~ Harmonica
TextualPython✓ macOS · Linux · Windows✓ widgets · CSS~ mouse · hover✓ animate() · easing~ textual serve · local only
RatatuiRust✓ macOS · Linux · Windows— immediate-mode~ backend only~ tachyonfx~ Ratzilla · static WASM
InkJS/React✓ macOS · Linux · Windows✓ React~ useAnimation · ticks only

Verified August 2026 against current releases: Bubble Tea 2.0 · Textual 8.2 · Ratatui 0.30 · Ink 7.1.  first-party · ~ partial, separate package, or raw mouse events ·  none · local only the app runs as a process you host and streams a terminal to the browser; there is no static build to deploy. SwiftTUI's browser paths render to the DOM with a real accessibility tree, not through a terminal emulator.

Coming from SwiftUI? The shape is the same; the terminal-native differences are deliberate and recorded in the open. Read the primer.

Showcase

Built with SwiftTUI.

The counter above fits on one screen. These do not. All four are maintained examples in swift-tui-examples: clone the repo and each one builds and runs with one command.

Browse every example

Further reading

Beyond the terminal

Terminal first, not terminal only. The same App runs in a browser: --web serves it on localhost, or build it with SwiftTUIWASI and ship a static bundle with @swifttui/web, as the demo above is. It also embeds in native apps through swift-tui-swiftui (macOS, iOS) and swift-tui-android (arm64 preview). No terminal emulators. Start from Hosts and Platforms.

Community

Build with us.

Development happens in public on GitHub, and the people building SwiftTUI are on Discord. Come ask questions, show what you are building, or talk through a contribution.