- Fix FocusID generation in 6 interactive views (Button, TextField, SecureField, Toggle, Slider, Stepper)
* Changed from user-data-based IDs ("button-Label") to nil (auto-generated from context.identity.path)
* Eliminates collision risk when multiple views have same label
* Example: two "Save" buttons now get unique IDs ("button-1.0.0", "button-1.0.1") instead of both "button-Save"
- Audit findings on disabled-state handling:
* All 9 interactive views already use consistent pattern: canBeFocused: !isDisabled
* Disabled color styling already standardized across Button, Toggle, Slider, Stepper, RadioButton
* List and Table lack explicit visual disabled styling (minor - functionality already correct)
- List API already migrated to modifiers:
* .focusID(), .listEmptyPlaceholder(), .listFooterSeparator() all exist
* Init parameters already removed (SwiftUI-compliant)
Changes:
- Sources/TUIkit/Views/Button.swift: focusID: String? init defaults to nil
- Sources/TUIkit/Views/TextField.swift: focusID init defaults to nil
- Sources/TUIkit/Views/SecureField.swift: focusID init defaults to nil
- Sources/TUIkit/Views/Toggle.swift: focusID init defaults to nil
- Sources/TUIkit/Views/Slider.swift: focusID init defaults to nil
- Sources/TUIkit/Views/Stepper.swift: focusID init defaults to nil (3 inits)
- Tests/TUIkitTests/ButtonTests.swift: Update test expectations for nil focusID default
Moved:
- .claude/plans/open/2026-02-10-layout-system-refactor.md → .claude/plans/done/
- Added completion notes: Phases 1-4 complete, 1034+ tests passing
Test Results: 1037/1037 tests passing ✅
TUIkit
Tip
☕ Support TUIkit Development
If you enjoy TUIkit and find it useful, consider supporting its development! Your donations help cover ongoing costs like hosting, tooling, and the countless cups of coffee that fuel late-night coding sessions. Every contribution, big or small, is greatly appreciated and keeps this project alive. Thank you! 💙
Important
This project is currently a WORK IN PROGRESS! I strongly advise against using it in a production environment because APIs are subject to change at any time.
A SwiftUI-like framework for building Terminal User Interfaces in Swift: no ncurses, no C dependencies, just pure Swift.
What is this?
TUIkit lets you build TUI apps using the same declarative syntax you already know from SwiftUI. Define your UI with View, compose views with VStack, HStack, and ZStack, style text with modifiers like .bold() and .foregroundColor(.red), and run it all in your terminal.
import TUIkit
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var count = 0
var body: some View {
VStack(spacing: 1) {
Text("Hello, TUIkit!")
.bold()
.foregroundColor(.cyan)
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
.statusBarItems {
StatusBarItem(shortcut: "q", label: "quit")
}
}
}
Features
Core
Viewprotocol: the core building block, mirroring SwiftUI'sView@ViewBuilder: result builder for declarative view composition@State: reactive state management with automatic re-rendering@Environment: dependency injection for theme, focus manager, status barAppprotocol: app lifecycle with signal handling and run loop
Views & Components
- Primitive views:
Text,EmptyView,Spacer,Divider - Layout containers:
VStack,HStack,ZStackwith alignment and spacing - Interactive:
Button,Toggle,Menuwith keyboard navigation - Data views:
List,Table,Section,ForEach - Containers:
Alert,Dialog,Panel,Box,Card - Feedback:
ProgressView(5 bar styles),Spinner(animated) StatusBar: context-sensitive keyboard shortcuts
Styling
- Text styling: bold, italic, underline, strikethrough, dim, blink, inverted
- Full color support: ANSI colors, 256-color palette, 24-bit RGB, hex values, HSL
- Theming: 6 predefined palettes (Green, Amber, Red, Violet, Blue, White)
- Border styles: rounded, line, double, thick, ASCII, and more
- List styles:
PlainListStyle,InsetGroupedListStylewith alternating rows - Badges:
.badge()modifier for counts and labels on list rows
Advanced
- Lifecycle modifiers:
.onAppear(),.onDisappear(),.task() - Storage:
@AppStorage,@SceneStoragewith JSON backend - Preferences: bottom-up data flow with
PreferenceKey - Focus system: Tab/Shift+Tab navigation,
.focusSection()for grouped areas - Render caching:
.equatable()for subtree memoization
Run the Example App
swift run TUIkitExample
Press q or ESC to exit.
Installation
Quick Start with CLI
Install the tuikit command and create a new project:
curl -fsSL https://raw.githubusercontent.com/phranck/TUIkit/main/project-template/install.sh | bash
tuikit init MyApp
cd MyApp && swift run
See project-template/README.md for more options (SQLite, Swift Testing).
Manual Setup
Add TUIkit to your Package.swift:
dependencies: [
.package(url: "https://github.com/phranck/TUIkit.git", branch: "main")
]
Then add it to your target:
.target(
name: "YourApp",
dependencies: ["TUIkit"]
)
Theming
TUIkit includes predefined palettes inspired by classic terminals:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.palette(SystemPalette(.green)) // Classic green terminal
}
}
Available palettes (all via SystemPalette):
.green: Classic P1 phosphor CRT (default).amber: P3 phosphor monochrome.red: IBM 3279 plasma.violet: Retro sci-fi terminal.blue: VFD/LCD displays.white: DEC VT100/VT220 (P4 phosphor)
Architecture
- No singletons for state: All state flows through the Environment system
- Pure ANSI rendering: No ncurses or other C dependencies
- Linux compatible: Works on macOS and Linux (XDG paths supported)
- Value types: Views are structs, just like SwiftUI
Project Structure
Sources/
├── TUIkit/
│ ├── App/ App, Scene, WindowGroup
│ ├── Core/ View, ViewBuilder, State, Environment, Color, Theme
│ ├── Modifiers/ Border, Frame, Padding, Overlay, Lifecycle
│ ├── Rendering/ Terminal, ANSIRenderer, ViewRenderer, FrameBuffer
│ └── Views/ Text, Stacks, Button, Menu, Alert, StatusBar, ...
└── TUIkitExample/ Example app (executable target)
Tests/
└── TUIkitTests/ 1039 tests across 143 test suites
Requirements
- Swift 6.0+
- macOS 10.15+ or Linux
Developer Notes
- Tests use Swift Testing (
@Test,#expect): run withswift test - All 1039 tests run in parallel
- The
Terminalclass handles raw mode and cursor control via POSIXtermios
Contribution
License
This repository has been published under the MIT license.
