A Go library for building beautiful, interactive command-line prompts and terminal UI components.
- Interactive Prompts: Text input, password, confirmation, single/multi-select, autocomplete, and multiline textarea
- Progress Indicators: Spinners with customizable frames and progress bars with multiple styles
- Output Utilities: Styled tables, boxed messages, and streaming output
- Modern API: Context-aware, generic types for select options, functional options pattern
- Cross-Platform: Unix and Windows terminal support
- Multiline Input: Textarea supports multiline editing with Shift+Enter for new lines and Up/Down navigation
go get github.com/yarlson/tapRequirements: Go 1.24+
package main
import (
"context"
"fmt"
"github.com/yarlson/tap"
)
func main() {
name := tap.Text(context.Background(), tap.TextOptions{
Message: "Enter your name:",
Placeholder: "Type something...",
})
fmt.Printf("Hello, %s!\n", name)
}colors := []tap.SelectOption[string]{
{Value: "red", Label: "Red", Hint: "The color of passion"},
{Value: "blue", Label: "Blue", Hint: "The color of calm"},
{Value: "green", Label: "Green", Hint: "The color of nature"},
}
result := tap.Select[string](context.Background(), tap.SelectOptions[string]{
Message: "What's your favorite color?",
Options: colors,
})
fmt.Printf("You selected: %s\n", result)spin := tap.NewSpinner(tap.SpinnerOptions{})
spin.Start("Connecting")
// ... do work ...
spin.Stop("Connected", 0)
// With hint (optional second line in gray)
spin.Stop("Installed 42 packages", 0, tap.StopOptions{
Hint: "Run 'npm start' to begin",
})progress := tap.NewProgress(tap.ProgressOptions{
Style: "heavy",
Max: 100,
Size: 40,
})
progress.Start("Downloading...")
for i := 0; i <= 100; i += 10 {
progress.Advance(10, fmt.Sprintf("Downloading... %d%%", i))
}
progress.Stop("Download complete!", 0)
// With hint (optional second line in gray)
progress.Stop("Download complete!", 0, tap.StopOptions{
Hint: "Saved to ~/Downloads/file.zip (128 MB)",
})res := tap.Textarea(context.Background(), tap.TextareaOptions{
Message: "Enter your commit message:",
Placeholder: "Type something...",
Validate: func(s string) error {
if len(s) < 10 {
return fmt.Errorf("at least 10 characters required")
}
return nil
},
})
fmt.Println(res)Use Shift+Enter to insert new lines. Pasted content is collapsed into [Text N] placeholders and expanded on submit.
// Message, Outro, and Cancel support optional hints
tap.Message("Task completed", tap.MessageOptions{
Hint: "Processed 42 items in 1.2s",
})
tap.Outro("All done!", tap.MessageOptions{
Hint: "Run 'app --help' for more options",
})| Key | Action |
|---|---|
Return (Enter) |
Submit/confirm selection |
Escape or ^C |
Cancel and exit prompt |
Left/Right |
Move cursor left/right |
Backspace/Del |
Delete character |
| Key | Action |
|---|---|
Shift+Return |
Insert new line (multiline input) |
Up/Down |
Move to previous/next line |
Home |
Move to start of current line |
End |
Move to end of current line |
Return |
Submit multiline text |
| Function | Description | Return Type |
|---|---|---|
Text(ctx, TextOptions) |
Single-line text input | string |
Password(ctx, PasswordOptions) |
Masked password input | string |
Confirm(ctx, ConfirmOptions) |
Yes/No confirmation | bool |
Select[T](ctx, SelectOptions[T]) |
Single-choice selection | T |
MultiSelect[T](ctx, MultiSelectOptions[T]) |
Multiple-choice selection | []T |
Textarea(ctx, TextareaOptions) |
Multiline text input | string |
Autocomplete(ctx, AutocompleteOptions) |
Text input with suggestions | string |
| Function | Description |
|---|---|
NewSpinner(SpinnerOptions) |
Animated spinner indicator |
NewProgress(ProgressOptions) |
Progress bar with percentage |
NewStream(StreamOptions) |
Streaming output with optional timer |
| Function | Description |
|---|---|
Table(headers, rows, TableOptions) |
Render formatted tables |
Box(message, title, BoxOptions) |
Render boxed messages |
Intro(title, ...MessageOptions) |
Display intro message |
Outro(message, ...MessageOptions) |
Display outro message |
Message(message, ...MessageOptions) |
Display styled message |
type TextOptions struct {
Message string
Placeholder string
DefaultValue string
InitialValue string
Validate func(string) error
Input Reader
Output Writer
}type TextareaOptions struct {
Message string // Prompt label displayed above the input area
Placeholder string // Hint text shown when input is empty
DefaultValue string // Value returned when user submits empty input
InitialValue string // Pre-populated editable content on prompt start
Validate func(string) error // Validates the fully-resolved string on submit
Input Reader
Output Writer
}Validate receives the resolved string with all paste placeholders expanded. Return an error to reject the input — the error message is displayed below the input and the user can continue editing.
type SelectOptions[T any] struct {
Message string
Options []SelectOption[T]
InitialValue *T
MaxItems *int
Input Reader
Output Writer
}type SpinnerOptions struct {
Indicator string // "dots" (default) or "timer"
Frames []string // custom animation frames
Delay time.Duration // frame delay
}type ProgressOptions struct {
Style string // "heavy", "light", or "block"
Max int // maximum value
Size int // bar width in characters
}type TableOptions struct {
ShowBorders bool
IncludePrefix bool
MaxWidth int
ColumnAlignments []TableAlignment
HeaderStyle TableStyle
HeaderColor TableColor
FormatBorder func(string) string
}type MessageOptions struct {
Output Writer
Hint string // Optional second line displayed in gray
}Used with Spinner.Stop() and Progress.Stop() to add an optional hint line:
type StopOptions struct {
Hint string // Optional second line displayed in gray below the message
}Run interactive examples:
go run ./examples/text/main.go
go run ./examples/textarea/main.go
go run ./examples/password/main.go
go run ./examples/select/main.go
go run ./examples/multiselect/main.go
go run ./examples/confirm/main.go
go run ./examples/autocomplete/main.go
go run ./examples/spinner/main.go
go run ./examples/progress/main.go
go run ./examples/messages/main.go
go run ./examples/table/main.go
go run ./examples/stream/main.goTerminal signal handling differs between Unix and Windows. The library includes platform-specific implementations:
internal/terminal/terminal_unix.go- Unix signal handlinginternal/terminal/terminal_windows.go- Windows signal handling
Shift+Enter (multiline input) requires a terminal that reports modifier keys. Supported terminals include iTerm2, kitty, Alacritty, WezTerm, Ghostty, and Windows Terminal. Terminals that don't distinguish Shift+Enter from Enter (e.g., macOS Terminal.app) cannot insert newlines via keyboard — users can paste multiline content instead.
Bracketed paste mode enables paste detection and [Text N] placeholder collapsing. Supported by all major modern terminals. Terminals without bracketed paste support degrade to character-by-character input — functional but without placeholder collapsing.
| Variable | Required | Description |
|---|---|---|
TERM |
No | Terminal type for ANSI escape sequence support |
go test ./...With race detection:
go test -race ./...The project uses golangci-lint with configuration in .golangci.yml:
golangci-lint runOverride terminal I/O for deterministic tests:
in := tap.NewMockReadable()
out := tap.NewMockWritable()
tap.SetTermIO(in, out)
defer tap.SetTermIO(nil, nil)
// Simulate keypresses
go func() {
in.EmitKeypress("h", tap.Key{Name: "h"})
in.EmitKeypress("i", tap.Key{Name: "i"})
in.EmitKeypress("", tap.Key{Name: "return"})
}()
result := tap.Text(ctx, tap.TextOptions{Message: "Enter:"})Symptom: Terminal signal handling differs between Unix and Windows
Solution: The library includes platform-specific implementations. Ensure you're building for the correct target platform. Check internal/terminal/terminal_unix.go and internal/terminal/terminal_windows.go for platform-specific behavior.
Symptom: Text width calculations may produce unexpected results with styled text
Solution: The library handles ANSI escape sequences in width calculations via visibleWidth() and truncateToWidth() functions in ansi_utils.go. Ensure styled text is properly formatted with reset sequences.
Contributions are welcome. When contributing:
- Follow existing code patterns and naming conventions
- Add unit tests using the mock I/O pattern
- Add examples under
examples/<feature>/main.gofor new features - Run
go test ./...andgolangci-lint runbefore submitting
MIT License - see LICENSE for details.