TerminalUI
No description available
Install / Use
/learn @1amageek/TerminalUIREADME
TerminalUI
A Swift library for building rich, interactive terminal user interfaces with a declarative SwiftUI-like DSL.
Features
- 🎨 Declarative DSL - Build terminal UIs with a familiar SwiftUI-like syntax
- 🚀 High Performance - Efficient differential rendering with reconciliation
- 🎭 Streamlined Components - Essential terminal UI components without bloat (14 total)
- 🌈 Smart Color Support - Automatic fallback from TrueColor → 256 → 16 colors
- 📊 Tracing Integration - Optional observability via swift-distributed-tracing
- ⚡ Async/Await - Modern Swift concurrency with actor-based runtime
- 🔧 Type-Safe - Compile-time safety with strong typing and Sendable conformance
Installation
Add TerminalUI to your Package.swift:
dependencies: [
.package(url: "https://github.com/yourusername/TerminalUI.git", from: "1.0.0")
]
Then add it as a dependency to your target:
.target(
name: "YourApp",
dependencies: ["TerminalUI"]
)
Quick Start
Simple Text Output
import TerminalUI
Text("Hello, Terminal!")
.foreground(.cyan)
.bold()
Output:
Hello, Terminal! // In cyan color with bold style
Progress Bar
ProgressView(label: "Downloading", value: 0.7)
Output:
Downloading [████████████████████████████░░░░░░░░░░░] 70%
Spinner Animation
Spinner("Loading...")
.style(.dots)
Output: (animated)
⠋ Loading...
⠙ Loading...
⠹ Loading...
⠸ Loading...
⠼ Loading...
⠴ Loading...
Layout with VStack
VStack {
Text("Terminal UI").bold()
Divider()
Text("Build beautiful terminal interfaces")
}
Output:
Terminal UI // Bold text
──────────────── // Horizontal line
Build beautiful terminal interfaces
Data Display with ForEach
let items = ["Apple", "Banana", "Cherry"]
VStack {
Text("Fruits:").bold()
ForEach(items, id: \.self) { item in
Text("• \(item)")
}
}
Output:
Fruits: // Bold text
• Apple
• Banana
• Cherry
Component Library (14 Total Components)
Layout Components (7)
- VStack - Vertical stacking container
- HStack - Horizontal stacking container
- Spacer - Flexible space that expands
- Divider - Horizontal line separator
- Panel - Box container with optional border and title
- Group - Logical grouping without visual representation
- EmptyView - View that renders nothing
// Vertical stack
VStack(alignment: .leading, spacing: 1) {
Text("Line 1")
Text("Line 2")
}
Output:
Line 1
Line 2
// Horizontal stack
HStack(spacing: 2) {
Text("Left")
Spacer()
Text("Right")
}
Output:
Left Right
// Panel with border
Panel(title: "Information") {
Text("Panel content here")
}
Output:
┌─ Information ─────────────┐
│ Panel content here │
└───────────────────────────┘
Text Components (2)
- Text - Basic text with style modifiers
- List - Display items in various list styles
// Styled text
Text("Important")
.foreground(.red)
.background(.yellow)
.bold()
.underline()
Output:
Important // Red text on yellow background, bold and underlined
// Lists with different styles
List(items: ["Apple", "Banana"], style: .bulleted)
List(items: ["First", "Second"], style: .numbered)
List(items: ["Task 1", "Task 2"], style: .checkbox)
Output:
• Apple
• Banana
1. First
2. Second
☐ Task 1
☐ Task 2
// Nested lists
List {
Text("Item 1")
List {
Text("Sub Item 1.1")
Text("Sub Item 1.2")
}
Text("Item 2")
}
Output:
• Item 1
◦ Sub Item 1.1
◦ Sub Item 1.2
• Item 2
Data Components (2)
- Table - Tabular data display with customizable columns
- Tree - Hierarchical tree structure display
// Table with columns
let columns = [
TableColumn(id: "name", title: "Name", width: .fixed(20)),
TableColumn(id: "status", title: "Status", width: .auto)
]
let rows = [
["name": "Service A", "status": "Running"],
["name": "Service B", "status": "Stopped"]
]
Table(columns: columns, rows: rows)
Output:
┌────────────────────┬──────────┐
│ Name │ Status │
├────────────────────┼──────────┤
│ Service A │ Running │
│ Service B │ Stopped │
└────────────────────┴──────────┘
// Tree structure
let rootNode = TreeNode(
id: "root",
label: "Root",
icon: "📁",
children: [
TreeNode(id: "child1", label: "Child 1", icon: "📄"),
TreeNode(id: "child2", label: "Child 2", icon: "📁",
children: [
TreeNode(id: "grandchild", label: "Grandchild", icon: "📄")
])
]
)
Tree(root: rootNode, showLines: true)
Output:
📁 Root
├── 📄 Child 1
└── 📁 Child 2
└── 📄 Grandchild
Progress Components (2)
- ProgressView - Determinate/indeterminate progress bars
- Spinner - Animated loading indicators
// Determinate progress
ProgressView(label: "Processing", value: 0.65)
Output:
Processing [██████████████████████████░░░░░░░░░░░░░░] 65%
// Indeterminate progress
ProgressView(label: "Loading", indeterminate: true)
Output: (animated)
Loading [░░░░░░████░░░░░░] // Moving bar animation
// Custom spinner styles
Spinner("Connecting...")
.style(.dots)
Output: (animated)
⠋ Connecting...
⠙ Connecting...
⠹ Connecting...
⠸ Connecting...
⠼ Connecting...
⠴ Connecting...
⠦ Connecting...
⠧ Connecting...
⠇ Connecting...
⠏ Connecting...
Input Components (3)
- TextField - Single-line text input
- Button - Interactive button with keyboard shortcuts
- Selector - Selection UI for multiple options with ForEach support
// Text input field
TextField(
label: "Username",
text: $username,
placeholder: "Enter username"
)
Output:
Username: [Enter username ]
↑ Cursor position (blinking)
// Button with action and keyboard shortcut
Button("Submit", shortcut: "s") {
// Handle action
}
Output:
[ Submit (s) ] // Highlighted when focused
// Selector with ForEach
let options = ["Option 1", "Option 2", "Option 3"]
Selector($selectedOption) {
ForEach(options, id: \.self) { option in
Text(option)
}
}
Output:
> Option 1 // Selected with arrow indicator
Option 2
Option 3
Control Flow
- ForEach - Iterate over collections with stable IDs
// ForEach with Identifiable items
struct Todo: Identifiable {
let id = UUID()
let title: String
}
let todos = [
Todo(title: "Write documentation"),
Todo(title: "Fix bugs")
]
VStack {
ForEach(todos) { todo in
Text("• \(todo.title)")
}
}
Output:
• Write documentation
• Fix bugs
// ForEach with ranges
VStack {
ForEach(0..<3) { index in
Text("Item \(index + 1)")
}
}
Output:
Item 1
Item 2
Item 3
Advanced Features
View Modifiers and Effects
// Text style modifiers
Text("Styled")
.foreground(.cyan)
.background(.blue)
.bold()
.italic()
.underline()
.dim()
Output:
Styled // Cyan text on blue background, bold, italic, underlined, dimmed
// Animation effects (via ViewModifiers)
Text("Loading...")
.shimmer(duration: 2.0)
Output: (animated)
Loading... // Shimmering effect moving across the text
Text("Alert!")
.blink(duration: 0.5)
Output: (animated)
Alert! // Text appears and disappears every 0.5 seconds
Live Rendering
For dynamic updates without full screen refresh:
// Single element updates
let renderer = LiveRenderer()
await renderer.render(view)
// Multiple elements with LiveSession
let session = LiveSession()
// Add/update elements at specific positions
await session.update("header", at: Point(x: 0, y: 0), with: headerView)
await session.update("content", at: Point(x: 0, y: 2), with: contentView)
await session.update("footer", at: Point(x: 0, y: 10), with: footerView)
// Remove element
await session.remove("content")
// Redraw all elements
await session.redrawAll()
Theming
// Use semantic colors
VStack {
Text("✓ Success").foreground(.semantic(.success))
Text("✗ Error").foreground(.semantic(.error))
Text("⚠ Warning").foreground(.semantic(.warning))
}
Output:
✓ Success // Green text
✗ Error // Red text
⚠ Warning // Yellow text
Conditional Rendering
// Conditional rendering example
VStack {
if isLoggedIn {
Text("Welcome back!")
} else {
Text("Please log in")
}
Divider()
switch status {
case .loading:
Spinner("Loading...")
case .success(let message):
Text(message).foreground(.green)
case .error(let error):
Text("Error: \(error)").foreground(.red)
}
}
Output (when logged in and loading):
Welcome back!
────────────────
⠋ Loading...
Output (when not logged in with error):
Please log in
────────────────
Error: Connection failed // In red color
Architecture
TerminalUI uses a virtual DOM-like approach with efficient reconciliation:
- Declarative Views → Node Tree → Reconciliation → Render Commands → Terminal Output
Key concepts:
- NodeKind: Enumeration of all componen
