Other TUI Libraries: JVM, Kotlin, Node.js & Swift

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

The big terminal user interface libraries have their own pages: Ratatui, Bubble Tea, Textual and Rich, Spectre.Console and Terminal.Gui and ncurses and FTXUI. This page covers the rest of the field: the libraries a team reaches for when the program is already written in Java, Kotlin, JavaScript or Swift and a full-screen interface is wanted without adding a second language. Four are alive and worth evaluating (Lanterna, TamboUI, Mosaic and Ink), one is effectively frozen (blessed and its fork neo-blessed) and one is a young project whose last commit is more than two years old (SwiftTUI). The running scenario is the Bookshelf shelf tui screen: a list of books and a details pane, kept small so the libraries can be compared on the same screen.

The page was written against Lanterna 3.1.5, TamboUI 0.6.0, Mosaic 0.18.0, Ink 8.0.0 with React 19.3.0 and Node.js 24, and SwiftTUI 0.1.0. The versions were read from Maven Central and the npm registry on the day the page was written; where a project’s README shows an older number, that is said. Only the Ink example was run: it was executed on Node.js 24 under a pseudo-terminal and its test was run with ink-testing-library. The Java, Kotlin and Swift snippets were not compiled or run while the page was written, because no JVM, Kotlin or Gradle toolchain is installed on the machine used for it, and the SwiftTUI package was not fetched and built there. They follow the examples in each project’s official documentation, which is linked after each one. None of the libraries was tested on Windows; Windows behaviour is taken from the documentation, and the documentation is silent on it for most of them, as the tables say.

The concepts shared by every library (the main loop, raw mode, the alternate screen and the guarantee to restore the terminal) are on TUI Architecture & Concepts. The escape sequences underneath are on Escape Sequences & Terminal Capabilities. The design rule that a full-screen interface must be a view over a command that also works without a terminal is on TUIs vs. CLIs, and the first look at Ink, JLine and SwiftTUI is on Node.js CLI Frameworks, Java CLI Frameworks and Swift & .NET CLI Frameworks. The Java and Kotlin command-line basics (arguments, Console, processes, startup time) are on Command-Line Programs on the JVM & Kotlin, and the Node.js terminal basics on Console, Readline & Terminal I/O.

The libraries at a glance

Library Language and version Paradigm Platforms stated by its documentation Status

Lanterna

Java; 3.1.5 (3.2.0-alpha1 also published)

Three layers: terminal, screen, and a window-based GUI toolkit (gui2) with components and layout managers

xterm-compatible terminals; a bundled Swing terminal emulator is used when a graphical environment (Windows or Xorg) is present. macOS is not mentioned

Released on Maven Central; the repository was last pushed to in July 2026. LGPL

TamboUI

Java; 0.6.0 (2026-10-12)

Three levels: immediate mode, a managed TuiRunner loop, and a declarative toolkit with CSS styling

Backends for JLine 3, Panama and Aesh. Operating systems are not stated on the pages read

Experimental. The README says "APIs may/will change". MIT

Mosaic

Kotlin; 0.18.0

Jetpack Compose compiler and runtime rendering to the terminal

Artifacts published for the JVM, JavaScript and native Linux, macOS and Windows (mingwx64) targets

Described as experimental by its README

Ink

JavaScript and TypeScript; 8.0.0

React renderer with flexbox layout (Yoga)

Node.js 22 or later; React 19.3 or later

Active; the most used of this group. MIT

blessed

JavaScript; 0.1.81

Curses-like widget library with a DOM-like API and its own terminfo parser

Not stated

The last release is from 2015; the fork neo-blessed is at 0.2.0 from 2018

SwiftTUI

Swift; 0.1.0

SwiftUI-style views, @State, Application(rootView:).start()

macOS and Linux

Last commit in July 2024; no later tag

Every library here takes over the terminal for a full-screen interface (raw mode, and for most of them the alternate screen), so every pitfall of that mode applies: the program must restore the terminal on every exit path, must not be started when standard input or output is not a terminal, and should be reachable only behind an explicit subcommand such as shelf tui. A library that does this for you (Ink does, as shown below) still leaves signals and uncaught exceptions to the program.

Lanterna

Lanterna is a pure-Java library for text-mode interfaces. Its README describes it as a library "for creating semi-graphical user interfaces in a text-only environment" with no native dependencies, in the style of curses. It is organised in three layers, each usable on its own:

  • com.googlecode.lanterna.terminal is direct terminal access: write characters and escape sequences, read keys.

  • com.googlecode.lanterna.screen is a buffered screen: a back buffer of cells that is diffed against what the terminal shows, so only changes are sent.

  • com.googlecode.lanterna.gui2 is the GUI toolkit, with modal windows, panels, labels, text boxes, buttons, list boxes, tables, menus, dialogs and layout managers.

DefaultTerminalFactory chooses the implementation at run time. On a machine with a graphical environment it opens a bundled Swing-based terminal emulator window instead of using standard output, which lets a developer run the program from an IDE (whose console is not a real terminal) and deploy the same code to a headless server. This is Lanterna’s answer to the "no TTY under the IDE" problem that Mosaic users meet (see below).

Topic Linux macOS Windows

Terminal

xterm-compatible terminals (konsole, gnome-terminal, PuTTY, xterm and others) through standard input and output

The README does not mention macOS; any xterm-compatible terminal is the stated requirement

The README names Windows as a graphical environment where the Swing emulator is used. It does not describe Windows console support

Java

The Maven POM of 3.1.5 compiles for Java 1.8; the README does not state a minimum

Same

Same

Build coordinates

com.googlecode.lanterna:lanterna:3.1.5 on Maven Central

Same

Same

Installing it is one dependency; the README’s Maven snippet still shows 3.1.2, while Maven Central lists 3.1.5 as the latest stable release and 3.2.0-alpha1 as a pre-release.

<dependency>
    <groupId>com.googlecode.lanterna</groupId>
    <artifactId>lanterna</artifactId>
    <version>3.1.5</version>
</dependency>

A Bookshelf window

The official hello-world example builds a terminal, wraps it in a screen, fills a Panel with a two-column GridLayout, and runs a MultiWindowTextGUI. The version below keeps that structure and changes the content to a book form: two labelled text boxes and a button that closes the window.

import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.DefaultWindowManager;
import com.googlecode.lanterna.gui2.EmptySpace;
import com.googlecode.lanterna.gui2.GridLayout;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;

import java.io.IOException;

public class ShelfAdd {
    public static void main(String[] args) throws IOException {
        // Terminal and screen layers.
        Terminal terminal = new DefaultTerminalFactory().createTerminal();
        Screen screen = new TerminalScreen(terminal);
        screen.startScreen();

        // The form: two rows of label and text box, then a button.
        Panel panel = new Panel();
        panel.setLayoutManager(new GridLayout(2));
        panel.addComponent(new Label("Title"));
        panel.addComponent(new TextBox());
        panel.addComponent(new Label("Author"));
        panel.addComponent(new TextBox());
        panel.addComponent(new EmptySpace(new TerminalSize(0, 0)));

        BasicWindow window = new BasicWindow("shelf add");
        panel.addComponent(new Button("Add", window::close));
        window.setComponent(panel);

        // Show the window and block until it closes.
        MultiWindowTextGUI gui = new MultiWindowTextGUI(
                screen, new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE));
        try {
            gui.addWindowAndWait(window);
        } finally {
            screen.stopScreen();
        }
    }
}

The try and finally around addWindowAndWait are not in the official example. They are there because the Lanterna guide says TextGUI has no start or stop methods: the program owns the Screen, starts it before drawing and stops it when finished, and stopScreen() is what restores the terminal. A form like this one needs a second step in a real program: reading the two text boxes with getText() after the window closes and handing the values to the same code that shelf add runs, so the TUI stays a view over the command.

Threads and limits

The documentation says Lanterna does not enforce a GUI thread. Its internal API is synchronised on a best-effort basis, but the guide recommends making every change to components from one designated GUI thread. By default MultiWindowTextGUI uses a same-thread strategy, so work from a background thread (a shelfd status poll, say) must be handed to the GUI thread rather than touching components directly. The documentation lists Hello World, panels, layout managers, labels, text boxes, buttons, combo boxes, check boxes, radio boxes, action list boxes, message and file dialogs, tables and menus as examples, which covers a catalogue browser; it does not document mouse-wheel scrolling, themes beyond the built-in ones, or testing support, and this page does not invent any.

TamboUI

TamboUI is a newer Java library for terminal user interfaces. The project README says it brings the paradigms of Rust’s Ratatui and Go’s Bubble Tea to Java, and the project site describes it as inspired by Ratatui and Textual; the two pages do not agree, and both are quoted rather than reconciled. It is declared experimental by its README, which also says "APIs may/will change", so pin an exact version and read the release notes at every upgrade, as is advised for Ratatui on Ratatui: Testing & Ecosystem.

The modules published on Maven Central under the group dev.tamboui at 0.6.0 map onto the layers of Ratatui:

Artifact Role

tamboui-core

Buffer, cell, rectangle, style, layout and text primitives

tamboui-widgets

The widget implementations: tables, charts, lists, gauges, calendars and others

tamboui-tui

The managed loop: TuiRunner, event handling and key helpers

tamboui-toolkit

A fluent DSL for declarative interfaces with components and focus management

tamboui-css

CSS styling for the toolkit, according to the site

tamboui-jline3-backend

Terminal backend on JLine 3

tamboui-panama-backend

Terminal backend on the Java foreign-function API (Panama)

tamboui-aesh-backend

Terminal backend on Aesh

tamboui-picocli

Optional picocli integration for argument parsing

Maven Central also lists artifacts for images, Markdown, effects and annotation processing, which the pages read do not describe; they are not covered here. The README states that the library requires Java 8 or later with Java 17 or later "highly recommended". The site says the same and adds that its quick-start example uses a Java 25 feature (an instance main method, so the source file has no class declaration). Native executables built with GraalVM are mentioned on the site; no figures for size or startup time are given there.

The README says TamboUI is "currently only available as snapshot builds", but Maven Central lists releases from 0.1.0 to 0.6.0, the latest published on 2026-10-12. The README text is therefore out of date; the Maven Central listing is what this page relies on.

Three levels, one screen

The site presents three ways in. Immediate mode is the lowest level: the program enables raw mode, enters the alternate screen, creates a Terminal over a backend and calls terminal.draw(...) itself, which is the same shape as Ratatui. TuiRunner is the middle level and owns the event loop and calls back with key, mouse and tick events. The toolkit is the top level: a declarative tree of components with focus management and CSS styling, comparable to Textual’s model on Textual and Rich.

The quick-start on the site is a toolkit program. Changing only the text to the Bookshelf, and running it with JBang as the site does, gives:

//DEPS dev.tamboui:tamboui-toolkit:LATEST
//DEPS dev.tamboui:tamboui-panama-backend:LATEST

void main() throws Exception {
    try (var runner = ToolkitRunner.create()) {
        runner.run(() ->
            markupText("[bold]shelf[/bold]: 3 books, [red]1 on loan[/red]. Press [blue]q[/] to exit!")
        );
    }
}
jbang ShelfHello.java

The site shows the body of main without import lines, and its LATEST version is JBang’s way of asking for the newest release; for a real build, replace it with 0.6.0. The README shows a different entry point for the same toolkit (a subclass of ToolkitApp whose render() returns a panel) and a TuiRunner example with a key handler and a Paragraph frame renderer, so the API of the toolkit has changed between the documents. Prefer the page that matches the version you install, and check it against the API documentation shipped with that version before copying code. The documentation read does not show a list-and-details screen for the toolkit, so this page does not provide one.

Backends and platforms

The choice of backend is the main operating-system decision. JLine 3 is the long-established option and is covered for Java in general on Java CLI Frameworks; the Panama backend avoids a native-library dependency by calling the operating system through the foreign-function API, which on the JDK is final from Java 22 (the page on the JVM covers the JDK 22 to 25 differences). Aesh is the third backend. The pages read do not state which backend works on which operating system, how Windows consoles are handled, or what the graceful fallback is without a terminal, so test the backend you pick on every platform you ship.

Mosaic

Mosaic, by Jake Wharton, applies the Jetpack Compose compiler and runtime to the terminal. The README describes it as "An experimental tool" and says it is inspired by Ink: composable functions describe the output, and when the state they read changes, the affected part of the screen is redrawn. The model is the one of Compose UI, with remember, mutableStateOf, LaunchedEffect and coroutines for background work; unlike Ink it needs no JavaScript runtime and, as the Maven Central listing shows, it is published for the JVM and JavaScript and for native Linux, macOS and Windows (mingwx64) targets, so a Kotlin/Native executable is possible (see the JVM page for Kotlin/Native executables).

Setup is a Gradle module with the Kotlin Compose compiler plugin applied and one dependency:

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.2.0"
    kotlin("plugin.compose") version "2.2.0"
}

dependencies {
    implementation("com.jakewharton.mosaic:mosaic-runtime:0.18.0")
}

The Kotlin version shown is an example, not a statement of what Mosaic 0.18.0 requires; the README says only to apply the JetBrains Kotlin Compose plugin to every module that calls runMosaic or defines a @Composable function.

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.jakewharton.mosaic.runMosaic
import com.jakewharton.mosaic.ui.Text
import kotlinx.coroutines.delay

suspend fun main() = runMosaic {
    var loans by remember { mutableIntStateOf(0) }

    Text("shelf: $loans books on loan")

    LaunchedEffect(Unit) {
        for (i in 1..3) {
            delay(500)
            loans = i
        }
    }
}

This is the shape of the README’s counter example (runMosaic, a remembered mutableIntStateOf, a LaunchedEffect with delay) with the text changed to the Bookshelf; the imports, which the README does not list in full, should be checked against the version installed. The README notes that getValue and setValue may need to be imported by hand for the by delegate. The README documents Text and the counter and a keyboard-driven robot sample; it does not describe an input API, a layout set (rows, columns, borders) or testing support in the text read for this page, so a shelf tui list-and-details screen is not shown.

Two pitfalls follow from how Mosaic is run. First, the README warns against running a Mosaic program through Gradle or an IDE, because neither provides a terminal and the output breaks; build the application and run the binary in a real terminal emulator. Second, because there is no documented input API in the pages read, treat a Mosaic program as an output renderer (progress and status views, for which it is an attractive alternative to hand-written escape sequences) until you have verified the keyboard support of the version you use.

Ink

Ink is "React for CLIs": components are rendered to the terminal and laid out with flexbox through Yoga. It is the most widely used library on this page and the one the AI section already introduces on Node.js CLI Frameworks. This page assumes that introduction and adds what changes in Ink 8.

Ink 8 requirements

The manifest of ink@8.0.0 on the npm registry declares Node.js 22 or later in its engines field and a peer dependency on React 19.3.0 or later; the earlier 7.x line used on the other pages needs React 19.2. Ink is an ES module package, so a program is an .mjs file or lives in a package whose package.json sets the field type to module. Upgrading from Ink 7 therefore means moving to Node.js 22 or later (Node.js 24 is the current LTS) and to React 19.3 or later; the changelog should be read for any other change, and the README of the development branch (which describes the upcoming version) is not a substitute for it.

A Bookshelf list and details pane

JSX needs a transpiler, so the example uses React.createElement and runs on plain Node.js. It shows a list on the left and a details pane on the right, arrow keys to move, l to loan or return, and q to quit.

{
  "name": "shelf-ink",
  "private": true,
  "type": "module"
}
npm install ink@8.0.0 react@19.3.0
node shelf-tui.mjs
import React, {useState} from 'react';
import {render, Box, Text, useInput, useApp} from 'ink';

const h = React.createElement; // JSX would need a build step (TypeScript, Babel or tsx)

const books = [
  {title: 'Dune', author: 'Frank Herbert', loaned: false},
  {title: 'Hyperion', author: 'Dan Simmons', loaned: true},
  {title: 'Neuromancer', author: 'William Gibson', loaned: false},
];

function Shelf() {
  const {exit} = useApp();
  const [index, setIndex] = useState(0);
  const [items, setItems] = useState(books);

  useInput((input, key) => {
    if (input === 'q') exit();
    if (key.upArrow) setIndex(i => Math.max(0, i - 1));
    if (key.downArrow) setIndex(i => Math.min(items.length - 1, i + 1));
    if (input === 'l') {
      setItems(list => list.map((b, i) => (i === index ? {...b, loaned: !b.loaned} : b)));
    }
  });

  const current = items[index];
  return h(Box, {flexDirection: 'row', borderStyle: 'round', paddingX: 1},
    h(Box, {flexDirection: 'column', width: 28},
      ...items.map((b, i) =>
        h(Text, {key: b.title, inverse: i === index}, `${i === index ? '>' : ' '} ${b.title}`))),
    h(Box, {flexDirection: 'column', marginLeft: 2},
      h(Text, {bold: true}, current.title),
      h(Text, null, current.author),
      h(Text, {color: current.loaned ? 'yellow' : 'green'}, current.loaned ? 'on loan' : 'available'),
      h(Text, {dimColor: true}, 'up/down: move  l: loan/return  q: quit')));
}

render(h(Shelf));

This program was run on Node.js 24 with Ink 8.0.0 and React 19.3.0 under a pseudo-terminal: it drew the bordered two-pane screen, moved the cursor on the down arrow, toggled the loan state with l and quit on q. The captured output showed that Ink 8 wraps each repaint in the synchronized-output sequences (DEC mode 2026, ESC [ ? 2026 h and ESC [ ? 2026 l), erases the previous frame line by line, and hides and shows the cursor around the session; the sequences are described on Escape Sequences & Terminal Capabilities.

Render options and terminal behaviour

render(tree, options) takes the options below, with the defaults the README documents. They are the Ink answers to the lifecycle questions every TUI library has.

Option Default Effect

exitOnCtrlC

true

Ink listens for Ctrl+C and exits. This exists because raw mode stops the terminal from turning Ctrl+C into SIGINT

patchConsole

true

Output from console.* calls is intercepted so that it does not collide with Ink’s drawing: Ink clears its output, prints the message and redraws

alternateScreen

false

Renders on the alternate screen; the original content is restored on exit and the scrollback buffer is unavailable meanwhile. Only in interactive mode

incrementalRendering

false

Updates only the lines that changed; lines with unsupported controls fall back to whole-line updates

maxFps

30

Caps how often the interface re-renders

interactive

auto-detected

false when CI is detected or standard output is not a terminal; overrides the detection when set

isScreenReaderEnabled

from INK_SCREEN_READER

Switches to screen-reader output (see below)

debug

false

Writes every update as separate output instead of replacing the previous one

kittyKeyboard

not set

Enables the Kitty keyboard protocol (mode of auto, enabled or disabled)

Two behaviours matter outside a terminal. When CI is detected (or standard output is not a terminal) Ink does not draw continuously and writes only the last frame when the app unmounts; CI=false restores full rendering. And a program that calls useInput while standard input is not a terminal fails: running the example above with < /dev/null made Ink print an error that raw mode is not supported on the current process.stdin. The documented guard is isRawModeSupported, returned by the useStdin hook; the run below used it to fall back to plain output.

import React from 'react';
import {render, Text, useStdin} from 'ink';

const h = React.createElement;

function App() {
  const {isRawModeSupported} = useStdin();
  return h(Text, null, isRawModeSupported ? 'interactive' : 'plain output only');
}

render(h(App));
node guard.mjs < /dev/null
plain output only

In a real shelf tui the check belongs before render, as on Node.js CLI Frameworks: refuse to start the TUI when either stream is not a terminal and point to shelf list. Node.js on Windows needs a terminal that processes virtual-terminal sequences, such as Windows Terminal or a modern console; the Ink README read for this page does not discuss Windows, so check the behaviour on the consoles you support, using Windows Console & Processes for the background.

Input, accessibility and tests

useInput((input, key) => ...) receives the typed string and a key object with flags such as upArrow, return and ctrl. A paste of more than one character arrives as one string, and when the separate usePaste hook is active the useInput handlers do not receive pasted text. isActive: false in the options disables a handler while others stay active, which is how focus between panes is usually implemented; useFocus and useFocusManager provide a ready-made version.

Ink is the one library on this page whose documentation addresses screen readers. Passing isScreenReaderEnabled to render, or setting INK_SCREEN_READER=true, switches to a text rendering; Ink implements a small subset of ARIA, so aria-role, aria-state, aria-label and aria-hidden on Box and Text change what is read (a box with the role checkbox and the state checked is rendered as the text (checked) checkbox: Accept terms and conditions). The README documents this switch, not a test of any particular screen reader; for the wider question see Accessibility & Internationalization.

Tests use ink-testing-library, which the Ink README recommends. A component is rendered to a string and lastFrame() is compared with the expected text:

import React from 'react';
import {render} from 'ink-testing-library';
import {Text} from 'ink';
import assert from 'node:assert/strict';

const h = React.createElement;
const {lastFrame, unmount} = render(h(Text, null, 'Dune - available'));
assert.equal(lastFrame(), 'Dune - available');
unmount();
console.log('ok');

This test ran on Node.js 24 with Ink 8.0.0 and ink-testing-library 4.0.0 and printed ok. Testing through a real pseudo-terminal, which is how the example above was exercised, is on Testing Terminal Apps & TUIs.

Prompt libraries for Node.js

Not every interactive Node.js tool needs a full-screen interface. For questions, selections and confirmations, the maintained prompt libraries are @inquirer/prompts (8.7.4) and @clack/prompts (1.8.1). Both print a short interactive prompt, return the answer and leave the terminal in its normal mode afterwards, which makes them a better fit than Ink for shelf add asking for a title when none was given. Their use, the non-TTY fallback and the other languages' equivalents are on Interactive Prompts & Line Editing. The versions were checked against the registry; the prompt snippets were not run for this page.

import {input, confirm} from '@inquirer/prompts';

const title = await input({message: 'Title of the book'});
const ok = await confirm({message: `Add "${title}" to the shelf?`, default: true});
console.log(ok ? `added ${title}` : 'cancelled');

blessed and neo-blessed

blessed describes itself as "a curses-like library with a high level terminal interface API for node.js". It is written entirely in JavaScript, reads terminfo and termcap itself rather than linking ncurses, and offers a DOM-like widget API: boxes, lists, forms, inputs, prompts, progress bars, tables, an embedded terminal and image display, with mouse and keyboard events that bubble through the element tree. It was the Node.js choice for full-screen interfaces for years, and dashboards such as terminal system monitors were built on it.

Its maintenance status is the reason for including it here. The npm registry lists 0.1.81 as the latest version, published in 2015; the repository, which is not archived, was last pushed to in March 2024. The fork neo-blessed has latest at 0.2.0, published in 2018. Neither README says that the project is abandoned, and the pages read for this page do not mention neo-blessed at all, so this is a finding from the registry and the repository’s activity, not a statement from the maintainers. The practical advice is to avoid choosing blessed or neo-blessed for new work, to keep an existing blessed program pinned and tested on the terminals you ship to, and to look at Ink (or at a binary built with a language from the other TUI pages) for a replacement. The blessed documentation does not state which operating systems are supported.

SwiftTUI

SwiftTUI brings the SwiftUI programming model to the terminal: a View with a body, stacks, text, buttons and text fields, @State and @Binding, and Application(rootView:).start(). Focus moves with the arrow keys. The README lists macOS and Linux, Swift 5.6 in its badge, and the MIT licence. The GitHub repository shows 116 commits, and the last commit is from July 2024; the latest tag is 0.1.0. That makes it suitable for a prototype or for learning, and a risk for a shipping product: check that it still builds with the current Swift toolchain (6.4.1 at the time of writing) before depending on it. The first look and the package manifest are on Swift & .NET CLI Frameworks; the Swift build side is on Command-Line Tools in Swift.

import Foundation
import SwiftTUI

struct ShelfView: View {
    @State var loaned = false

    var body: some View {
        VStack(alignment: .leading) {
            Text("Dune - Frank Herbert").bold()
            Text(loaned ? "on loan" : "available")
            Button(loaned ? "Return" : "Loan") { loaned.toggle() }
        }
        .border()
        .padding(1)
    }
}

Application(rootView: ShelfView()).start()

This snippet was not compiled for this page. It uses the constructs the README lists as working (stacks, Text, Button, @State, Application(rootView:).start()); the modifiers bold(), border() and padding(_:) and the alignment: argument are taken from the SwiftUI vocabulary SwiftTUI mirrors, and the README text read does not list them, so verify them against the checked-out sources. The smaller view on the AI-section page, VStack, Text and Button inside Application(rootView:).start(), is the one that was compiled against Swift 6.4. Windows is not listed among the platforms. For a Swift tool that must be a real product today, a sensible route is to keep the interface in another library or to drive a terminal library through a C interface.

Choosing between them

If the program is Choose Because

Java, and the user may run it from an IDE or over SSH

Lanterna

Mature, released and documented; its Swing emulator makes development without a real terminal possible; Java 8 bytecode

Java, new code, and you want a modern immediate-mode or declarative API

TamboUI, with a pinned version

The only library here with an immediate-mode layer and a declarative toolkit; experimental, so budget for API changes

Kotlin, mostly output (progress and status views)

Mosaic

Compose model, no extra runtime; experimental, input not documented in the pages read

Kotlin, with menus and prompts but no custom layout

Mordant, see the JVM page

Prompts and tables without a full-screen model

JavaScript or TypeScript, interactive dashboard

Ink 8

Active, flexbox layout, documented screen-reader mode and a test library; needs Node.js 22 and React 19.3 or later

JavaScript, a few questions

@inquirer/prompts or @clack/prompts

No full-screen mode, no raw-mode lifecycle to manage

JavaScript, an existing blessed program

Keep blessed pinned; plan a migration

No release since 2015

Swift, a prototype of a SwiftUI-like terminal view

SwiftTUI

The only SwiftUI-style option; dormant, macOS and Linux only

Anything that must ship to Windows, macOS and Linux with a small binary

One of the libraries on the other TUI pages, such as Ratatui or Bubble Tea

Static native binaries and a documented Windows story; see Native Binaries & Cross-Compilation

The same decision as a flow, followed by the rule that applies to all of them:

flowchart TD A["Need a full-screen interface?"] -->|No| P["Use a prompt library or plain output"] A -->|Yes| B{"Which language is the program in?"} B -->|Java| C["Lanterna, or TamboUI pinned to one version"] B -->|Kotlin| D["Mosaic for output, Mordant for prompts"] B -->|JavaScript or TypeScript| E["Ink 8 on Node.js 22 or later"] B -->|Swift| F["SwiftTUI for a prototype only"] B -->|Other or must ship one small binary| G["Ratatui, Bubble Tea, Textual, Terminal.Gui, ncurses or FTXUI"]

Whatever the library, start the interface only behind its own subcommand, keep every operation available without it, refuse to run it when standard input or output is not a terminal, and test it through a pseudo-terminal on every operating system you ship.

Pitfalls

  • Starting a full-screen UI without a terminal. Ink fails when raw mode is unsupported on standard input; Mosaic output breaks under Gradle or an IDE; Lanterna may open a Swing window on a desktop machine instead of using the terminal. Check isatty-style conditions first.

  • Trusting a README for versions. The Lanterna README shows 3.1.2 while 3.1.5 is current, and the TamboUI README says no releases exist while Maven Central has six. Read the package registry, and quote the version you tested.

  • Not restoring the terminal. Lanterna needs screen.stopScreen() on every path; Ink does it on exit() and on Ctrl+C by default; SwiftTUI and Mosaic document nothing beyond normal exit. An uncaught exception or SIGTERM can leave the terminal in raw mode; the lifecycle is on Terminals, TTYs & Pseudo-Terminals.

  • Concurrency. Updating Lanterna components from a background thread is not safe; Ink state must change through React; Mosaic state through Compose state. Follow each library’s own model.

  • Untrusted text. Book titles and loan notes that come from a database or a user can contain escape sequences. Whether each library strips control characters from text is not documented in the pages read, so sanitise text yourself; the risk is described on Escape Sequences & Terminal Capabilities.

  • Accessibility. Only Ink documents a screen-reader mode among these libraries. For the others, keep a plain command-line equivalent (shelf list) and see Accessibility & Internationalization.