🚀 Tap
Beautiful, interactive command-line prompts for Go - A Go port of the popular TypeScript Clack library.
⚠️ Heavy Development: This project is currently in heavy development. APIs may change, and some features are still being implemented. Use with caution in production environments.
Why Tap?
Building interactive CLI applications shouldn't be complex. Tap brings the elegant, minimal design philosophy of Clack to the Go ecosystem, offering:
- Event-driven architecture for responsive, race-condition-free prompts
- Beautiful styling with consistent visual design and Unicode symbols
- Type-safe APIs with generic support for strongly-typed selections
- Comprehensive testing with built-in mock utilities
- Minimal dependencies - Only essential terminal and keyboard libraries
Features
✅ Available Now
Core Components:
- Text Input - Single-line input with cursor navigation, validation, placeholders, and default values
- Password Input - Masked text input for sensitive data
- Confirm - Yes/No prompts with customizable labels and keyboard navigation
- Select - Single selection from typed options with hints and color-coded display
- Progress Bar - Animated progress indicators with multiple styles (light, heavy, block)
- Spinner - Loading indicators with dots, timer, or custom frames
- Message Primitives - Intro, outro, cancel messages, and styled boxes
- Event System - Race-condition-free event loop architecture
Developer Experience:
- Mock Testing - Built-in utilities for reliable prompt testing
- Type Safety - Generic APIs for strongly-typed selections
- Terminal Management - Raw mode handling, keyboard input, and cursor control
🔄 Coming Soon
- Multi-Select - Multiple selection from lists with checkboxes
- Autocomplete - Text input with suggestion dropdown
- Group - Grouped prompts for complex workflows
Installation
go get github.com/yarlson/tap@latest
Quick Start
Usage
Use the tap package:
package main
import (
"fmt"
"github.com/yarlson/tap"
)
func main() {
// Use package-level helpers (auto-inits a session under the hood)
name := tap.Text(tap.TextOptions{Message: "What's your name?"})
if tap.IsCancel(name) {
tap.Cancel("Operation cancelled.")
return
}
confirmed := tap.Confirm(tap.ConfirmOptions{Message: fmt.Sprintf("Hello %v! Continue?", name)})
if tap.IsCancel(confirmed) {
tap.Cancel("Operation cancelled.")
return
}
if confirmed.(bool) {
tap.Outro("Let's go! 🎉")
}
}
Optionally manage a session explicitly:
package main
import (
"fmt"
"github.com/yarlson/tap"
)
func main() {
s, err := tap.Init() // or tap.New()
if err != nil { panic(err) }
defer tap.CloseDefault() // or s.Close()
name := tap.Text(tap.TextOptions{Message: "What's your name?"})
if tap.IsCancel(name) { tap.Cancel("Operation cancelled."); return }
confirmed := tap.Confirm(tap.ConfirmOptions{Message: fmt.Sprintf("Hello %v! Continue?", name)})
if tap.IsCancel(confirmed) { tap.Cancel("Operation cancelled."); return }
if confirmed.(bool) { tap.Outro("Let's go! 🎉") }
}
API Examples
Text Input with Validation (tap API)
import "errors"
email := tap.Text(tap.TextOptions{
Message: "Enter your email:",
Placeholder: "user@example.com",
DefaultValue: "anonymous@example.com",
Validate: func(input string) error {
if !strings.Contains(input, "@") {
return errors.New("Please enter a valid email")
}
return nil
},
})
pwd := tap.Password(tap.PasswordOptions{Message: "Enter your password:"})
if tap.IsCancel(pwd) { tap.Cancel("Operation cancelled."); return }
fmt.Printf("Password length: %d\n", len(pwd.(string)))
Confirmation with Custom Labels
proceed := tap.Confirm(tap.ConfirmOptions{
Message: "Deploy to production?",
Active: "Deploy",
Inactive: "Cancel",
InitialValue: false,
})
Type-Safe Selection
type Environment string
envs := []tap.SelectOption[Environment]{
{Value: "dev", Label: "Development", Hint: "Local development"},
{Value: "staging", Label: "Staging", Hint: "Pre-production testing"},
{Value: "prod", Label: "Production", Hint: "Live environment"},
}
env := tap.Select(tap.SelectOptions[Environment]{
Message: "Choose deployment target:",
Options: envs,
})
Progress Bar
prog := tap.NewProgress(tap.ProgressOptions{
Style: "heavy", // "light", "heavy", or "block"
Max: 100, // total units of work
Size: 40, // bar width in characters
})
prog.Start("Processing...")
for i := 0; i <= 100; i += 10 {
time.Sleep(200 * time.Millisecond)
prog.Advance(10, fmt.Sprintf("Step %d/10", i/10+1))
}
prog.Stop("Complete!", 0) // 0=success, 1=cancel, 2=error
Spinner
// Default spinner (dots)
spin := tap.NewSpinner(tap.SpinnerOptions{})
spin.Start("Connecting")
// ... do work ...
spin.Stop("Connected", 0)
// Timer indicator
timerSpin := tap.NewSpinner(tap.SpinnerOptions{Indicator: "timer"})
timerSpin.Start("Fetching data")
// ... do work ...
timerSpin.Stop("Done", 0)
// Custom frames and delay
customSpin := tap.NewSpinner(tap.SpinnerOptions{Frames: []string{"-", "\\", "|", "/"}, Delay: 100 * time.Millisecond})
customSpin.Start("Working")
// ... do work ...
customSpin.Stop("Complete", 0)
Architecture
Tap uses a clean, layered architecture designed for performance and reliability:
Package Structure
core - Core prompt engine with unstyled, functional prompts
prompts - Beautifully styled prompts built on top of core
terminal - Terminal management, keyboard input, and cursor control
Event-Driven Design
All prompts use a pure event loop architecture for race-condition-free operation:
// Single event loop processes all state changes
for event := range prompt.events {
event(&state) // Update state
prompt.render(&state) // Render changes
prompt.updateSnapshot(&state) // Update atomic snapshot
}
This approach eliminates the need for mutexes while providing excellent performance and thread safety through atomic snapshots.
Testing
Tap includes comprehensive test coverage with built-in mock utilities:
# Run all tests
go test ./...
# Test with race detection
go test -race ./...
# Test specific packages
go test ./prompts -v
go test ./core -v
Mock Testing Example
func TestTextPrompt(t *testing.T) {
mockInput := core.NewMockReader()
mockOutput := core.NewMockWriter()
// Simulate user typing "hello" and pressing enter
mockInput.SendString("hello")
mockInput.SendKey("return")
result := prompts.Text(prompts.TextOptions{
Message: "Enter text:",
Input: mockInput,
Output: mockOutput,
})
assert.Equal(t, "hello", result)
}
Examples
Explore working examples in the examples/ directory:
# Try different prompt types
go run examples/text/main.go # Text input with validation
go run examples/password/main.go # Password input (masked)
go run examples/confirm/main.go # Yes/No confirmations
go run examples/select/main.go # Single selection menus
go run examples/progress/main.go # Progress bars and status
go run examples/spinner/main.go # Spinners (dots, timer, custom frames)
go run examples/multiple/main.go # Complete workflow example
Each example demonstrates different features and can serve as starting points for your applications.
Development Status
Tap is in active development. Core functionality is stable and tested, but APIs may evolve. Current status:
- ✅ Stable: Text, Confirm, Select prompts with full styling
- ✅ Stable: Progress bars and message primitives
- ✅ Stable: Event loop architecture and terminal management
- 🔄 In Progress: Additional prompt types and enhanced styling
- 📋 Planned: Documentation site, themes, and advanced features
Contributing
Contributions welcome! Areas where help is needed:
- New Prompt Types - Multi-select, autocomplete
- Enhanced Styling - Themes, custom symbols, color schemes
- Documentation - API docs, tutorials, more examples
- Testing - Cross-platform testing, edge cases, performance
- Bug Reports - Issues with keyboard handling or rendering
Development Setup
git clone https://github.com/yarlson/tap.git
cd tap
go test ./... # Run tests
go run examples/text/main.go # Try examples
Please follow Go best practices, maintain test coverage above 80%, and use the event-driven architecture patterns established in the codebase.
License
MIT License - see LICENSE file for details.
Acknowledgments
- Clack - The original TypeScript library that inspired this project
- @eiannone/keyboard - Cross-platform keyboard input for Go
- The Go community for excellent tooling and libraries
Links
Built with ❤️ for developers who value simplicity and speed.