Please purchase the course to watch this video.

Full Course
Outputting text in command-line interface (CLI) applications can be enhanced through the use of colored text, which helps distinguish different types of messages, such as errors and successes. This functionality leverages ANSI escape codes, a standard for controlling text properties like color and font styling in terminal emulators. By inserting specific ANSI codes into the output stream, developers can easily format text to stand out; for instance, using code 31 results in red text for errors, while code 32 produces green text for success messages. The implementation involves constructing an abstraction that simplifies color setting and allows for additional features, such as bold text. Furthermore, there is an opportunity to create a color package within the UI framework to streamline and extend color usage, including the addition of background colors and more advanced 256-color support, all of which enriches user experience in CLI applications.
When it comes to outputting text with CLI applications, one common feature you may notice is that sometimes text can have colored output. This is useful for a number of applications in order to present information to users in a way that's visually recognizable:
- Error messages - Red text for errors
- Success messages - Green text for confirmations
- Warnings - Yellow text for cautions
- Information - Blue text for informational messages
- Highlighting - Different colors for emphasis
Current State vs Goal
Currently: Our applications only print text using the base text color of the user's configured terminal.
Goal: Print colored text like this:
- ERROR (red)
- SUCCESS (green)
- WARNING (yellow)
Understanding ANSI Escape Codes
To achieve colored output, we need to use ANSI escape codes, which are a standard for controlling various properties of text terminals or terminal emulators, including:
- Colors (foreground and background)
- Font styling (bold, italic, underline)
- Cursor positioning
- Text formatting
ANSI Escape Code Structure
All ANSI escape codes follow this pattern:
\033[{code}m
Components:
\033
- The escape character (ESC in octal)[
- Left bracket{code}
- The specific command codem
- Terminator character
Basic Color Implementation
Let's start with a simple example to make error text red:
Raw ANSI Escape Example
main.go (basic colored output):
package main
import "fmt"
func main() {
// Print "ERROR" in red
fmt.Printf("\033[31mERROR\033[0m\n")
// Print "SUCCESS" in green
fmt.Printf("\033[32mSUCCESS\033[0m\n")
}
Understanding the Code
fmt.Printf("\033[31mERROR\033[0m\n")
// ┌─────┐ ┌─────┐
// │ Set │ │Reset│
// │ Red │ │Color│
Breaking it down:
\033[31m
- Set foreground color to redERROR
- The text to display\033[0m
- Reset all formatting (very important!)\n
- New line
Testing Basic Colors
go run main.go
You should see:
- ERROR in red text
- SUCCESS in green text
ANSI Color Reference
Standard Foreground Colors (30-37)
Color | Code | Example |
---|---|---|
Black | 30 | \033[30m |
Red | 31 | \033[31m |
Green | 32 | \033[32m |
Yellow | 33 | \033[33m |
Blue | 34 | \033[34m |
Magenta | 35 | \033[35m |
Cyan | 36 | \033[36m |
White | 37 | \033[37m |
Standard Background Colors (40-47)
Color | Code | Example |
---|---|---|
Black | 40 | \033[40m |
Red | 41 | \033[41m |
Green | 42 | \033[42m |
Yellow | 43 | \033[43m |
Blue | 44 | \033[44m |
Magenta | 45 | \033[45m |
Cyan | 46 | \033[46m |
White | 47 | \033[47m |
Text Formatting Codes
Style | Code | Example |
---|---|---|
Reset | 0 | \033[0m |
Bold | 1 | \033[1m |
Dim | 2 | \033[2m |
Italic | 3 | \033[3m |
Underline | 4 | \033[4m |
The Importance of Resetting
Problem: Color Bleeding
// ❌ Without reset - color "bleeds" to subsequent text
fmt.Printf("\033[31mERROR")
fmt.Printf("This text is also red!") // Unintended!
Solution: Always Reset
// ✅ With reset - only target text is colored
fmt.Printf("\033[31mERROR\033[0m")
fmt.Printf("This text is normal color") // Correct!
Combining Multiple Styles
You can combine multiple ANSI codes by separating them with semicolons:
// Bold red text
fmt.Printf("\033[1;31mBOLD RED ERROR\033[0m\n")
// Bold text with yellow background
fmt.Printf("\033[1;43mBOLD ON YELLOW\033[0m\n")
// Underlined cyan text
fmt.Printf("\033[4;36mUNDERLINED CYAN\033[0m\n")
Testing Combined Styles
main.go (combined styles):
package main
import "fmt"
func main() {
// Regular colors
fmt.Printf("\033[31mERROR\033[0m\n")
fmt.Printf("\033[32mSUCCESS\033[0m\n")
// Bold colors
fmt.Printf("\033[1;31mBOLD ERROR\033[0m\n")
fmt.Printf("\033[1;32mBOLD SUCCESS\033[0m\n")
// Background colors
fmt.Printf("\033[41;37mWHITE ON RED BACKGROUND\033[0m\n")
fmt.Printf("\033[42;30mBLACK ON GREEN BACKGROUND\033[0m\n")
}
Building a Color Package
Now let's create a reusable color package for our UI library:
Project Structure:
color-demo/
├── main.go
└── ui/
└── color/
└── color.go
Basic Color Functions
ui/color/color.go (basic implementation):
package color
import "fmt"
// Red returns text colored in red
func Red(text string) string {
return fmt.Sprintf("\033[31m%s\033[0m", text)
}
// Green returns text colored in green
func Green(text string) string {
return fmt.Sprintf("\033[32m%s\033[0m", text)
}
// Yellow returns text colored in yellow
func Yellow(text string) string {
return fmt.Sprintf("\033[33m%s\033[0m", text)
}
// Blue returns text colored in blue
func Blue(text string) string {
return fmt.Sprintf("\033[34m%s\033[0m", text)
}
Using the Color Package
main.go (using color package):
package main
import (
"fmt"
"./ui/color" // Replace with your module path
)
func main() {
fmt.Println(color.Red("ERROR: Something went wrong!"))
fmt.Println(color.Green("SUCCESS: Operation completed!"))
fmt.Println(color.Yellow("WARNING: This is a warning!"))
fmt.Println(color.Blue("INFO: Additional information"))
}
Creating Helper Functions
To avoid repetition, let's create helper functions:
ui/color/color.go (with helpers):
package color
import "fmt"
// escape creates an ANSI escape sequence
func escape(code string) string {
return fmt.Sprintf("\033[%sm", code)
}
// wrap wraps text with color codes and reset
func wrap(code, text string) string {
return fmt.Sprintf("%s%s%s", escape(code), text, escape("0"))
}
// Standard colors using helpers
func Red(text string) string { return wrap("31", text) }
func Green(text string) string { return wrap("32", text) }
func Yellow(text string) string { return wrap("33", text) }
func Blue(text string) string { return wrap("34", text) }
func Magenta(text string) string { return wrap("35", text) }
func Cyan(text string) string { return wrap("36", text) }
func White(text string) string { return wrap("37", text) }
// Text formatting
func Bold(text string) string { return wrap("1", text) }
func Underline(text string) string { return wrap("4", text) }
func Italic(text string) string { return wrap("3", text) }
// Combined formatting
func BoldRed(text string) string {
return fmt.Sprintf("\033[1;31m%s\033[0m", text)
}
func BoldGreen(text string) string {
return fmt.Sprintf("\033[1;32m%s\033[0m", text)
}
Advanced: 256-Color Support
Modern terminals support 256 colors using extended ANSI codes:
256-Color Format
\033[38;5;{color_number}m # Foreground
\033[48;5;{color_number}m # Background
Where {color_number}
is 0-255.
Color Ranges:
- 0-15: Standard colors (same as basic ANSI)
- 16-231: 216 color cube (6×6×6 RGB)
- 232-255: 24 grayscale colors
ui/color/color.go (256-color support):
package color
import (
"fmt"
"os"
"strings"
)
// supports256Color checks if terminal supports 256 colors
func supports256Color() bool {
term := os.Getenv("TERM")
return strings.Contains(term, "256color") ||
strings.Contains(term, "xterm")
}
// Color256 sets foreground color using 256-color palette
func Color256(colorNumber int, text string) string {
if !supports256Color() {
// Fallback to nearest standard color
return fallbackColor(colorNumber, text)
}
return fmt.Sprintf("\033[38;5;%dm%s\033[0m", colorNumber, text)
}
// Background256 sets background color using 256-color palette
func Background256(colorNumber int, text string) string {
if !supports256Color() {
return text // Or fallback to standard background
}
return fmt.Sprintf("\033[48;5;%dm%s\033[0m", colorNumber, text)
}
// fallbackColor maps 256 colors to nearest standard color
func fallbackColor(colorNumber int, text string) string {
switch {
case colorNumber < 16:
// Use standard colors for 0-15
if colorNumber < 8 {
return wrap(fmt.Sprintf("%d", 30+colorNumber), text)
}
return wrap(fmt.Sprintf("%d", 82+colorNumber), text) // Bright colors
case colorNumber >= 232:
// Grayscale - use white or black
if colorNumber > 243 {
return White(text)
}
return wrap("30", text) // Black
default:
// Color cube - rough approximation
r := (colorNumber - 16) / 36
g := ((colorNumber - 16) % 36) / 6
b := (colorNumber - 16) % 6
// Convert to nearest standard color
if r > g && r > b {
return Red(text)
} else if g > r && g > b {
return Green(text)
} else if b > r && b > g {
return Blue(text)
}
return text // Default
}
}
Testing 256-Color Support
func main() {
fmt.Println("Terminal:", os.Getenv("TERM"))
fmt.Println("256-color support:", color.supports256Color())
// Test 256 colors
for i := 0; i < 16; i++ {
fmt.Printf("%s ", color.Color256(i, fmt.Sprintf("Color %d", i)))
}
fmt.Println()
// Test color cube (sample)
for i := 16; i < 32; i++ {
fmt.Printf("%s ", color.Color256(i, "█"))
}
fmt.Println()
}
Real-World Usage Patterns
Log Level Colors
package main
import (
"fmt"
"time"
"./ui/color"
)
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
)
func (l LogLevel) String() string {
switch l {
case DEBUG:
return color.Cyan("DEBUG")
case INFO:
return color.Blue("INFO")
case WARN:
return color.Yellow("WARN")
case ERROR:
return color.Red("ERROR")
default:
return "UNKNOWN"
}
}
func log(level LogLevel, message string) {
timestamp := time.Now().Format("15:04:05")
fmt.Printf("[%s] %s %s\n", timestamp, level, message)
}
func main() {
log(DEBUG, "Application starting...")
log(INFO, "Connected to database")
log(WARN, "High memory usage detected")
log(ERROR, "Failed to save user data")
}
Status Indicators
func showStatus(name string, success bool) {
status := "✗"
colorFunc := color.Red
if success {
status = "✓"
colorFunc = color.Green
}
fmt.Printf("%s %s %s\n", colorFunc(status), name,
color.Yellow("(checking...)"))
}
func main() {
showStatus("Database connection", true)
showStatus("Redis connection", true)
showStatus("External API", false)
}
Progress with Colors
func coloredProgressBar(progress float64, width int) string {
filled := int(progress * float64(width))
empty := width - filled
var bar strings.Builder
// Green for completed portion
bar.WriteString(color.Green(strings.Repeat("█", filled)))
// Gray for empty portion
bar.WriteString(color.Cyan(strings.Repeat("░", empty)))
percentage := int(progress * 100)
return fmt.Sprintf("%s %3d%%", bar.String(), percentage)
}
func main() {
for i := 0; i <= 10; i++ {
progress := float64(i) / 10.0
fmt.Printf("\r%s", coloredProgressBar(progress, 30))
time.Sleep(200 * time.Millisecond)
}
fmt.Println()
}
Homework Assignments
Assignment 1: Complete Color Palette
Implement all standard ANSI colors:
// TODO: Implement remaining foreground colors
func Black(text string) string { /* implement */ }
func Magenta(text string) string { /* implement */ }
func Cyan(text string) string { /* implement */ }
// TODO: Implement background colors
func RedBg(text string) string { /* implement */ }
func GreenBg(text string) string { /* implement */ }
func YellowBg(text string) string { /* implement */ }
func BlueBg(text string) string { /* implement */ }
// ... continue for all background colors
// TODO: Implement text formatting
func Bold(text string) string { /* implement */ }
func Italic(text string) string { /* implement */ }
func Underline(text string) string { /* implement */ }
func Dim(text string) string { /* implement */ }
Assignment 2: Advanced 256-Color Implementation
Create a comprehensive 256-color system:
// TODO: Implement RGB color conversion
func RGB(r, g, b int, text string) string {
// Convert RGB (0-255) to 256-color palette
// Formula: 16 + (36 * r/255 * 5) + (6 * g/255 * 5) + (b/255 * 5)
}
// TODO: Implement color palette display
func ShowColorPalette() {
// Display all 256 colors in a grid format
}
// TODO: Implement terminal capability detection
func GetColorSupport() (basic bool, extended bool, truecolor bool) {
// Check TERM environment variable
// Test color capability
}
Assignment 3: Color Themes
Create predefined color themes:
type Theme struct {
Error func(string) string
Warning func(string) string
Success func(string) string
Info func(string) string
}
var (
DefaultTheme = Theme{
Error: Red,
Warning: Yellow,
Success: Green,
Info: Blue,
}
DarkTheme = Theme{
Error: BoldRed,
Warning: BoldYellow,
Success: BoldGreen,
Info: Cyan,
}
// TODO: Create more themes
MonochromeTheme = Theme{ /* implement */ }
HighContrastTheme = Theme{ /* implement */ }
)
// TODO: Implement theme switching
func SetTheme(theme Theme) {
// Set global theme
}
Assignment 4: Terminal Compatibility
Handle different terminal capabilities:
// TODO: Implement capability detection
func detectTerminalCapabilities() TerminalInfo {
// Check TERM, COLORTERM environment variables
// Test actual color output capability
// Detect terminal emulator type
}
type TerminalInfo struct {
Name string
SupportsColor bool
Supports256 bool
SupportsTrueColor bool
Width int
Height int
}
// TODO: Implement graceful degradation
func SafeColor(colorFunc func(string) string, text string) string {
// Use color if supported, plain text otherwise
}
Testing Your Color Implementation
Visual Test Suite
// color_test.go
package color
import (
"fmt"
"testing"
)
func TestColorOutput(t *testing.T) {
// Visual test - run manually to verify colors
if testing.Short() {
t.Skip("Skipping visual color test")
}
fmt.Println("=== Color Test ===")
fmt.Println(Red("Red text"))
fmt.Println(Green("Green text"))
fmt.Println(Yellow("Yellow text"))
fmt.Println(Blue("Blue text"))
fmt.Println(BoldRed("Bold red text"))
fmt.Println("=== End Test ===")
}
func TestColorCodes(t *testing.T) {
// Test ANSI code generation
red := Red("test")
expected := "\033[31mtest\033[0m"
if red != expected {
t.Errorf("Red() = %q, want %q", red, expected)
}
}
func Benchmark256Color(b *testing.B) {
for i := 0; i < b.N; i++ {
Color256(196, "benchmark text")
}
}
Manual Testing Script
// test_colors.go
package main
import (
"fmt"
"./ui/color"
)
func main() {
fmt.Println("=== Basic Colors ===")
fmt.Println(color.Red("Red"), color.Green("Green"),
color.Yellow("Yellow"), color.Blue("Blue"))
fmt.Println("\n=== Background Colors ===")
fmt.Println(color.RedBg("Red BG"), color.GreenBg("Green BG"))
fmt.Println("\n=== Text Formatting ===")
fmt.Println(color.Bold("Bold"), color.Italic("Italic"),
color.Underline("Underline"))
fmt.Println("\n=== 256 Colors (Sample) ===")
for i := 0; i < 16; i++ {
fmt.Printf("%s ", color.Color256(i, "██"))
}
fmt.Println()
fmt.Println("\n=== Terminal Info ===")
fmt.Printf("TERM: %s\n", os.Getenv("TERM"))
fmt.Printf("256-color support: %t\n", color.supports256Color())
}
Performance Considerations
String Building Efficiency
For high-frequency color output, consider using strings.Builder
:
func coloredList(items []string) string {
var builder strings.Builder
for i, item := range items {
if i%2 == 0 {
builder.WriteString(Green(item))
} else {
builder.WriteString(Blue(item))
}
builder.WriteString("\n")
}
return builder.String()
}
Conditional Coloring
Only apply colors when output is to a terminal:
import (
"os"
"golang.org/x/term"
)
func smartColor(colorFunc func(string) string, text string) string {
if term.IsTerminal(int(os.Stdout.Fd())) {
return colorFunc(text)
}
return text // Plain text for pipes/files
}
Common Pitfalls and Solutions
Problem 1: Colors in Non-Interactive Environments
Issue: ANSI codes appear as garbage in logs or pipes.
Solution:
func isInteractive() bool {
return term.IsTerminal(int(os.Stdout.Fd()))
}
func ConditionalRed(text string) string {
if isInteractive() {
return Red(text)
}
return text
}
Problem 2: Nested Color Functions
Issue:
// This doesn't work as expected
fmt.Println(Red(Bold("text"))) // Double reset codes
Solution:
func RedBold(text string) string {
return fmt.Sprintf("\033[1;31m%s\033[0m", text)
}
Problem 3: String Length Calculation
Issue: ANSI codes affect string length calculations.
Solution:
import "regexp"
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
func stripANSI(str string) string {
return ansiRegex.ReplaceAllString(str, "")
}
func visualLength(str string) int {
return len(stripANSI(str))
}
Summary
We've successfully implemented colored terminal output for CLI applications:
Key Concepts Learned:
- ANSI Escape Codes - Standard for terminal text formatting
- Color Codes - Foreground (30-37) and background (40-47) colors
- Text Formatting - Bold, italic, underline using ANSI codes
- Reset Sequences - Always reset formatting to prevent bleeding
- Terminal Capability Detection - Check for color support
Benefits:
- Enhanced UX - Visual distinction for different message types
- Better Accessibility - Color coding for quick recognition
- Professional Appearance - Modern CLI applications expect color
- Flexible Theming - Customizable color schemes
Next Steps:
Complete the homework assignments to create a comprehensive color package with full 256-color support and terminal compatibility. In the next lesson, we'll explore image processing operations in Go.
Additional Resources
- ANSI Escape Code Reference - Complete list of codes and capabilities
- Terminal Compatibility - Testing different terminal emulators
- 256-Color Charts - Visual reference for extended colors
- Accessibility Guidelines - Color-blind friendly design patterns
Remember: Always provide fallbacks for environments that don't support color, and test your colored output in different terminals! 🌈
Complete Color Palette
Implement all standard ANSI colors including foreground, background, and text formatting functions.
Requirements:
Remaining Foreground Colors:
// TODO: Implement remaining foreground colors
func Black(text string) string { /* implement */ }
func Magenta(text string) string { /* implement */ }
func Cyan(text string) string { /* implement */ }
Background Colors:
// TODO: Implement background colors
func RedBg(text string) string { /* implement */ }
func GreenBg(text string) string { /* implement */ }
func YellowBg(text string) string { /* implement */ }
func BlueBg(text string) string { /* implement */ }
// ... continue for all background colors (Black, Magenta, Cyan, White)
Text Formatting:
// TODO: Implement text formatting
func Bold(text string) string { /* implement */ }
func Italic(text string) string { /* implement */ }
func Underline(text string) string { /* implement */ }
func Dim(text string) string { /* implement */ }
Testing: Create a comprehensive test that displays all colors and formatting options.
Advanced 256-Color Implementation
Create a comprehensive 256-color system with RGB conversion and terminal capability detection.
Requirements:
RGB Color Conversion:
// TODO: Implement RGB color conversion
func RGB(r, g, b int, text string) string {
// Convert RGB (0-255) to 256-color palette
// Formula: 16 + (36 * r/255 * 5) + (6 * g/255 * 5) + (b/255 * 5)
}
Color Palette Display:
// TODO: Implement color palette display
func ShowColorPalette() {
// Display all 256 colors in a grid format
}
Terminal Capability Detection:
// TODO: Implement terminal capability detection
func GetColorSupport() (basic bool, extended bool, truecolor bool) {
// Check TERM environment variable
// Test color capability
}
Implementation Notes:
- Handle terminals that don't support 256 colors with graceful fallbacks
- Test the RGB conversion formula accuracy
- Display colors in an organized, readable grid format
Color Themes
Create predefined color themes for different use cases and environments.
Requirements:
Theme Structure:
type Theme struct {
Error func(string) string
Warning func(string) string
Success func(string) string
Info func(string) string
}
var (
DefaultTheme = Theme{
Error: Red,
Warning: Yellow,
Success: Green,
Info: Blue,
}
DarkTheme = Theme{
Error: BoldRed,
Warning: BoldYellow,
Success: BoldGreen,
Info: Cyan,
}
// TODO: Create more themes
MonochromeTheme = Theme{ /* implement */ }
HighContrastTheme = Theme{ /* implement */ }
)
Theme Switching:
// TODO: Implement theme switching
func SetTheme(theme Theme) {
// Set global theme
}
Additional Themes to Implement:
- MonochromeTheme - For terminals without color support
- HighContrastTheme - For accessibility/visibility
- PastelTheme - Softer colors using 256-color palette
- RetroTheme - Classic terminal colors
Terminal Compatibility
Handle different terminal capabilities and provide graceful degradation for unsupported environments.
Requirements:
Capability Detection:
// TODO: Implement capability detection
func detectTerminalCapabilities() TerminalInfo {
// Check TERM, COLORTERM environment variables
// Test actual color output capability
// Detect terminal emulator type
}
type TerminalInfo struct {
Name string
SupportsColor bool
Supports256 bool
SupportsTrueColor bool
Width int
Height int
}
Graceful Degradation:
// TODO: Implement graceful degradation
func SafeColor(colorFunc func(string) string, text string) string {
// Use color if supported, plain text otherwise
}
Environment Variable Checks:
TERM
- Terminal type identificationCOLORTERM
- Color capability hintsNO_COLOR
- Respect color disabling conventionFORCE_COLOR
- Force color output
Terminal Types to Support:
- Standard terminals (xterm, xterm-256color)
- Windows Command Prompt
- PowerShell
- VS Code integrated terminal
- tmux/screen multiplexers
Testing: Create tests that work across different terminal environments and provide appropriate fallbacks.