components

package
v0.12.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 3, 2025 License: MIT Imports: 9 Imported by: 0

README

BubblyUI Components Package

Pre-built, production-ready TUI components for the BubblyUI framework.

Version: 3.0
Status: Stable
Coverage: ~88%


🎉 Unified Component Pattern

All components now use the same pattern!

As of the latest refactor, all BubblyUI components - both custom app components and built-in components from this package - use the same unified pattern.

✅ Use ExposeComponent for Everything

For ALL components including those in pkg/components:

// In Setup:
inputComp := components.Input(components.InputProps{
    Value:       valueRef,
    Placeholder: "Enter text...",
    Width:       50,
})

// ✅ UNIFIED PATTERN: Use ExposeComponent for all components!
if err := ctx.ExposeComponent("inputComp", inputComp); err != nil {
    ctx.Expose("error", err)
    return
}

// In Template:
inputComp := ctx.Get("inputComp").(bubbly.Component)
return inputComp.View()

Benefits:

  • ✅ Consistent pattern across all components
  • ✅ Automatic initialization (no manual .Init() needed)
  • ✅ Proper parent-child relationships
  • ✅ Works with DevTools component tree
  • ✅ Simpler, cleaner code
How This Was Achieved

The Input component (the only one with special requirements due to bubbles/textinput integration) was refactored to use WithMessageHandler internally. This allows it to work seamlessly with ExposeComponent without conflicts.

All 27 components now support ExposeComponent: Input, Button, Text, Badge, Icon, Spacer, Spinner, Checkbox, Radio, Toggle, Select, Textarea, Form, Table, List, Card, Modal, Tabs, Menu, Accordion, AppLayout, PageLayout, PanelLayout, GridLayout, and all others.


📦 Component Categories

Atoms (Basic Building Blocks)
  • Input - Text input with cursor, validation, password mode
  • Button - Clickable buttons (Primary, Secondary variants)
  • Text - Styled text labels and content
  • Badge - Status indicators and counts
  • Icon - Icon display component
  • Spacer - Layout spacing component
  • Spinner - Loading indicators
Molecules (Form Components)
  • Checkbox - Boolean checkbox inputs
  • Radio - Radio button groups
  • Toggle - Boolean switch/toggle
  • Select - Dropdown selection
  • Textarea - Multi-line text input
  • Form - Form wrapper with validation
Organisms (Data Display)
  • Table - Tabular data with columns, sorting, selection
  • List - Vertical list with custom rendering
  • Card - Content cards with title/content
  • Modal - Overlay dialogs
Navigation
  • Tabs - Tabbed interface
  • Menu - Menu navigation
  • Accordion - Expandable/collapsible sections
Templates (Layout Structures)
21. AppLayout

Description: Full application layout with header, sidebar, main content, and footer.

API:

func AppLayout(props AppLayoutProps) bubbly.Component

type AppLayoutProps struct {
    Header  bubbly.Component
    Sidebar bubbly.Component
    Main    bubbly.Component
    Footer  bubbly.Component
}

Example:

// Create header
headerText := components.Text(components.TextProps{
    Content: "My Application",
    Bold:    true,
    Color:   lipgloss.Color("99"),
})
headerText.Init()

header := components.Card(components.CardProps{
    Title:       headerText.View(),
    Background:  lipgloss.Color("236"),
    BorderStyle: lipgloss.NormalBorder(),
})
header.Init()

// Create sidebar navigation
navItems := []string{"Dashboard", "Users", "Products", "Orders", "Settings"}
selectedNav := bubbly.NewRef(0)

sidebarList := components.List(components.ListProps{
    Items:         navItems,
    SelectedIndex: selectedNav.Get(),
    BorderStyle:   lipgloss.NormalBorder(),
})
sidebarList.Init()

// Main content
mainContent := components.Card(components.CardProps{
    Title:       "Welcome",
    Content:     "Main application content goes here.",
    BorderStyle: lipgloss.RoundedBorder(),
})
mainContent.Init()

// Footer
footerText := components.Text(components.TextProps{
    Content: "© 2025 My Application | v1.0.0",
    Color:   lipgloss.Color("240"),
})
footerText.Init()

footer := components.Card(components.CardProps{
    Content:     footerText.View(),
    Background:  lipgloss.Color("233"),
    BorderStyle: lipgloss.NormalBorder(),
})
footer.Init()

// Create app layout
appLayout := components.AppLayout(components.AppLayoutProps{
    Header:  header,
    Sidebar: sidebarList,
    Main:    mainContent,
    Footer:  footer,
})
appLayout.Init()

Output:

╔══════════════════════════════════════════════════════════════╗
║ My Application                                               ║
╠══════════════════════════════════════════════════════════════╣
║          │                                                   ║
║ ● Dash…  │  ╭─────────────────────────────────────────────╮  ║
║   Users  │  │  Welcome                                    │  ║
║   Pro…   │  │                                             │  ║
║   Orders │  │  Main application content goes here.      │  ║
║   Set…   │  ╰─────────────────────────────────────────────╯  ║
║          │                                                   ║
╠══════════╪═══════════════════════════════════════════════════╣
║ © 2025 My Application | v1.0.0                               ║
╚══════════════════════════════════════════════════════════════╝

Features:

  • Common layout - Standard app structure
  • Flexible components - Any component as header/sidebar/main/footer
  • Responsive - Adapts to terminal size
  • Consistent spacing - Built-in layout rules
22. PageLayout

Description: Page-level layout with title, content, and optional actions.

API:

func PageLayout(props PageLayoutProps) bubbly.Component

type PageLayoutProps struct {
    Title    string
    Content  string
    Actions  []bubbly.Component  // Optional buttons
    Subtitle string
}

Example:

// Create action buttons
saveBtn := components.Button(components.ButtonProps{
    Label:   "Save",
    Variant: components.ButtonPrimary,
    OnClick: func() { savePage() },
})
saveBtn.Init()

cancelBtn := components.Button(components.ButtonProps{
    Label:   "Cancel",
    Variant: components.ButtonSecondary,
    OnClick: func() { cancel() },
})
cancelBtn.Init()

// Page content
content := `
This is a form-based page with various inputs and controls.
Users can fill out the form and take actions using the buttons below.
`

// Create page layout
page := components.PageLayout(components.PageLayoutProps{
    Title:    "Edit User Profile",
    Subtitle: "Update user information and preferences",
    Content:  content,
    Actions:  []bubbly.Component{saveBtn, cancelBtn},
})
page.Init()

// Detail view without actions
detailPage := components.PageLayout(components.PageLayoutProps{
    Title:    "Order Details",
    Subtitle: "Order #12345 - Placed on 2025-01-15",
    Content:  renderOrderDetails(order),
})
detailPage.Init()

// Welcome page
welcomePage := components.PageLayout(components.PageLayoutProps{
    Title:       "Welcome to My App",
    Subtitle:    "Getting started with our platform",
    Content:     welcomeContent,
    Actions:     []bubbly.Component{getStartedBtn, learnMoreBtn},
})
welcomePage.Init()

Output:

╭─────────────────────────────────────────────────────────╮
│                                                         │
│  Edit User Profile                                      │
│  Update user information and preferences                │
│                                                         │
│  This is a form-based page with various inputs and      │
│  controls. Users can fill out the form and take actions │
│  using the buttons below.                               │
│                                                         │
│  ┌────────┐  ┌──────────┐                               │
│  │  Save  │  │  Cancel  │                               │
│  └────────┘  └──────────┘                               │
│                                                         │
╰─────────────────────────────────────────────────────────╯

Features:

  • Page structure - Consistent page formatting
  • Title hierarchy - Title + optional subtitle
  • Action footer - Buttons at bottom
  • Flexible content - Any string content
  • Clean styling - Professional appearance
23. GridLayout

Description: Grid-based layout with rows and columns.

API:

func GridLayout(props GridLayoutProps) bubbly.Component

type GridLayoutProps struct {
    Columns     int               // Number of columns
    Rows        int               // Number of rows
    Cells       []bubbly.Component // Grid in row-major order
    Border      bool
    BorderStyle lipgloss.Border
}

Example:

// Dashboard with metrics
metric1 := components.Card(components.CardProps{
    Title:   "Total Users",
    Content: fmt.Sprintf("%d", totalUsers),
})
metric1.Init()

metric2 := components.Card(components.CardProps{
    Title:   "Active Now",
    Content: fmt.Sprintf("%d", activeUsers),
})
metric2.Init()

metric3 := components.Card(components.CardProps{
    Title:   "Revenue",
    Content: fmt.Sprintf("$%.2f", revenue),
})
metric3.Init()

metric4 := components.Card(components.CardProps{
    Title:   "Growth",
    Content: fmt.Sprintf("+%.1f%%", growth),
})
metric4.Init()

// 2x2 grid
dashboardGrid := components.GridLayout(components.GridLayoutProps{
    Columns: 2,
    Rows:    2,
    Cells: []bubbly.Component{
        metric1, metric2,  // Row 1
        metric3, metric4,  // Row 2
    },
    Border:      true,
    BorderStyle: lipgloss.RoundedBorder(),
})
dashboardGrid.Init()

// User list grid
userCards := []bubbly.Component{}
for _, user := range users {
    card := components.Card(components.CardProps{
        Title:   user.Name,
        Content: user.Email,
        Width:   40,
    })
    card.Init()
    userCards = append(userCards, card)
}

// 3-column responsive grid
gallery := components.GridLayout(components.GridLayoutProps{
    Columns: 3,
    Rows:    (len(userCards) + 2) / 3,  // Ceiling division
    Cells:   userCards,
    Border:  false,
})
gallery.Init()

Output:

╭──────────────┬──────────────╮
│ Total Users  │ Active Now   │
│ 1,234        │ 89           │
├──────────────┼──────────────┤
│ Revenue      │ Growth       │
│ $45,678.90   │ +12.5%       │
╰──────────────┴──────────────╯

Features:

  • Grid structure - Rows and columns
  • Auto-sizing - Cells auto-size to content
  • Flexible layout - Border optional
  • Responsive - Adapts to available space
  • Component grid - Any component in each cell
24. PanelLayout

Description: Panel with title, content, and optional sections.

API:

func PanelLayout(props PanelLayoutProps) bubbly.Component

type PanelLayoutProps struct {
    Title       string
    Content     string
    Sections    []PanelSection  // Optional sub-sections
    BorderStyle lipgloss.Border
}

type PanelSection struct {
    Title   string
    Content string
}

Example:

// Simple panel
infoPanel := components.PanelLayout(components.PanelLayoutProps{
    Title:       "System Information",
    Content:     "OS: Linux 5.15\nArch: x86_64\nGo Version: 1.22",
    BorderStyle: lipgloss.RoundedBorder(),
})
infoPanel.Init()

// Multi-section panel
detailsPanel := components.PanelLayout(components.PanelLayoutProps{
    Title:   "User Profile: Alice",
    Content: "Account created: Jan 15, 2025\nLast login: 2 hours ago",
    Sections: []components.PanelSection{
        {
            Title:   "Contact Information",
            Content: "Email: alice@example.com\nPhone: +1-555-0123",
        },
        {
            Title:   "Preferences",
            Content: "Theme: Dark\nLanguage: English\nNotifications: Enabled",
        },
        {
            Title:   "Activity",
            Content: "Posts: 42\nComments: 189\nLast activity: Jan 20, 2025",
        },
    },
})
detailsPanel.Init()

Output:

╭─────────────────────────────────────────────╮
│ System Information                          │
├─────────────────────────────────────────────┤
│ OS: Linux 5.15                              │
│ Arch: x86_64                                │
│ Go Version: 1.22                            │
╰─────────────────────────────────────────────╯

╭─────────────────────────────────────────────╮
│ User Profile: Alice                         │
├─────────────────────────────────────────────┤
│ Account created: Jan 15, 2025               │
│ Last login: 2 hours ago                     │
├─────────────────────────────────────────────┤
│ Contact Information                         │
├─────────────────────────────────────────────┤
│ Email: alice@example.com                    │
│ Phone: +1-555-0123                          │
├─────────────────────────────────────────────┤
│ Preferences                                 │
├─────────────────────────────────────────────┤
│ Theme: Dark                                 │
│ Language: English                           │
│ Notifications: Enabled                      │
├─────────────────────────────────────────────┤
│ Activity                                    │
├─────────────────────────────────────────────┤
│ Posts: 42                                   │
│ Comments: 189                               │
│ Last activity: Jan 20, 2025                 │
╰─────────────────────────────────────────────╯

Features:

  • Sectioned content - Multiple sub-sections
  • Collapsible - Each section independent
  • Hierarchical info - Clean organization
  • Consistent styling - Borders between sections

🔗 Integration with Other Packages

Integration with pkg/bubbly
import (
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
)

func createDashboard() (bubbly.Component, error) {
    return bubbly.NewComponent("Dashboard").
        Setup(func(ctx *bubbly.Context) {
            // Use BubblyUI reactive primitives
            users := bubbly.NewRef([]User{})
            loading := bubbly.NewRef(true)
            
            // Fetch users
            ctx.OnMounted(func() {
                go func() {
                    data := loadUsers()
                    users.Set(data)
                    loading.Set(false)
                }()
            })
            
            // Expose to template
            ctx.Expose("users", users)
            ctx.Expose("loading", loading)
        }).
        Template(func(ctx bubbly.RenderContext) string {
            if ctx.Get("loading").(*bubbly.Ref[bool]).Get() {
                spinner := components.Spinner(components.SpinnerProps{
                    Message: "Loading users...",
                })
                spinner.Init()
                return spinner.View()
            }
            
            users := ctx.Get("users").(*bubbly.Ref[[]User]).Get()
            
            // Create table from data
            rows := [][]string{}
            for _, user := range users {
                rows = append(rows, []string{
                    user.Name,
                    user.Email,
                    user.Role,
                    user.Status,
                })
            }
            
            table := components.Table(components.TableProps{
                Headers: []string{"Name", "Email", "Role", "Status"},
                Rows:    rows,
            })
            table.Init()
            
            return table.View()
        }).
        Build()
}
Integration with pkg/bubbly/composables
import (
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
    composables "github.com/newbpydev/bubblyui/pkg/bubbly/composables"
)

func createUserForm() (bubbly.Component, error) {
    return bubbly.NewComponent("UserForm").
        Setup(func(ctx *bubbly.Context) {
            // Form state
            formData := composables.UseForm(ctx, UserForm{
                Name:  "",
                Email: "",
                Role:  "user",
            }, 
            func(f UserForm) map[string]string {
                errors := make(map[string]string)
                if f.Name == "" {
                    errors["Name"] = "Name is required"
                }
                if f.Email == "" {
                    errors["Email"] = "Email is required"
                }
                return errors
            })
            
            // Async submit
            submit := composables.UseAsync(ctx, func() error {
                return api.CreateUser(formData.Values.Get())
            })
            
            // Handle submit
            ctx.On("submit", func(_ interface{}) {
                formData.Submit()
                if formData.IsValid.Get() {
                    submit.Execute()
                }
            })
            
            // Expose to template
            ctx.Expose("formData", formData)
            ctx.Expose("submit", submit)
        }).
        Template(func(ctx bubbly.RenderContext) string {
            formData := ctx.Get("formData").(composables.UseFormReturn[UserForm])
            
            // Create form from formData
            form := components.Form(components.FormProps[UserForm]{
                Initial: formData.Values.Get(),
                Fields: []components.FormField{
                    {
                        Name:  "Name",
                        Label: "Full Name",
                        Component: components.Input(components.InputProps{
                            Value: formData.Values.Get().Name,
                        }),
                    },
                    // ... other fields
                },
                OnSubmit: func(data UserForm) error {
                    ctx.Emit("submit", nil)
                    return nil
                },
            })
            form.Init()
            
            return form.View()
        }).
        Build()
}
Integration with pkg/bubbly/router
import (
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
    csrouter "github.com/newbpydev/bubblyui/pkg/bubbly/router"
)

func createNav() (bubbly.Component, error) {
    return bubbly.NewComponent("Navigation").
        Setup(func(ctx *bubbly.Context) {
            router := csrouter.NewRouter().
                AddRoute("/", homeComponent).
                AddRoute("/users", userListComponent).
                AddRoute("/users/:id", userDetailComponent).
                Build()
            
            ctx.Provide("router", router)
        }).
        Template(func(ctx bubbly.RenderContext) string {
            router := ctx.Get("router").(*csrouter.Router)
            currentRoute := router.CurrentRoute()
            
            // Create navigation list
            routes := []string{"/", "/users", "/settings"}
            routeNames := []string{"Home", "Users", "Settings"}
            
            items := []string{}
            currentPath := currentRoute.Path
            
            for i, path := range routes {
                prefix := "  "
                if currentPath == path {
                    prefix = "● "
                }
                items = append(items, prefix+routeNames[i])
            }
            
            nav := components.List(components.ListProps{
                Items: items,
            })
            nav.Init()
            
            return nav.View()
        }).
        Build()
}
Integration with pkg/bubbly/devtools
import (
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
    "github.com/newbpydev/bubblyui/pkg/bubbly/devtools"
)

func createMonitoredComponent() (bubbly.Component, error) {
    renderCount := bubbly.NewRef(0)
    
    return bubbly.NewComponent("Monitored").
        Setup(func(ctx *bubbly.Context) {
            // Expose state to devtools
            data := composables.UseAsync(ctx, fetchData)
            ctx.Expose("data", data.Data)
            ctx.Expose("loading", data.Loading)
            
            // Track render performance
            ctx.OnUpdated(func() {
                renderCount.Set(renderCount.Get() + 1)
                
                if devtools.IsEnabled() {
                    devtools.GetMetricsTracker().RecordRenderTime(
                        "Monitored", 
                        5*time.Millisecond,
                    )
                }
            })
        }).
        Template(func(ctx bubbly.RenderContext) string {
            if ctx.Get("loading").(*bubbly.Ref[bool]).Get() {
                spinner := components.Spinner(components.SpinnerProps{
                    Message: "Loading...",
                })
                spinner.Init()
                return spinner.View()
            }
            
            table := components.Table(components.TableProps{
                Headers: []string{"ID", "Name", "Status"},
                Rows:    ctx.Get("data").(*bubbly.Ref[[][]string]).Get(),
            })
            table.Init()
            
            return table.View()
        }).
        Build()
}

📊 Performance Characteristics

Benchmarks
Component Performance:
============================

Atoms:
  Button:           < 500 ns/op    (render)
  Text:             < 300 ns/op    (render)
  Icon:             < 400 ns/op    (render)
  Badge:            < 600 ns/op    (render)
  Spinner:          < 1 μs/op      (render with animation)
  Spacer:           < 200 ns/op    (render)

Molecules:
  Input:            < 2 μs/op      (render)
  Checkbox:         < 1 μs/op      (render)
  Select:           < 3 μs/op      (render)
  TextArea:         < 5 μs/op      (render)
  Toggle:           < 1 μs/op      (render)
  Radio:            < 2 μs/op      (render)

Organisms:
  Form:             < 10 μs/op     (render 5 fields)
  Table:            < 50 μs/op     (render 100 rows)
  List:             < 100 μs/op    (render 1000 items with virtual scrolling)
  Card:             < 3 μs/op      (render)
  Modal:            < 2 μs/op      (render)
  Tabs:             < 5 μs/op      (render 5 tabs)
  Accordion:        < 10 μs/op     (render 3 sections)
  Menu:             < 2 μs/op      (render)

Templates:
  AppLayout:        < 15 μs/op     (render 4 regions)
  PageLayout:       < 3 μs/op      (render)
  GridLayout:       < 20 μs/op     (render 4 cells)
  PanelLayout:      < 8 μs/op      (render 3 sections)

Memory:

  • Components: 1-2 KB per instance (negligible)
  • Large tables: ~1 KB per row (strings cached)
  • Virtual scrolling: 50-100 rows rendered at once (not all 1000+)
Optimization Tips
  1. Reuse components - Don't recreate on every render:
// ✅ Good: Create once, reuse
var cachedButton bubbly.Component

Setup(func(ctx *bubbly.Context) {
    cachedButton = components.Button(components.ButtonProps{
        Label:   "Save",
        OnClick: saveFunc,
    })
    cachedButton.Init()
})

Template(func(ctx bubbly.RenderContext) string {
    return cachedButton.View()  // Reuse
})

// ❌ Bad: Recreate every render
Template(func(ctx bubbly.RenderContext) string {
    button := components.Button(...)  // New allocation
    button.Init()                     // Init every time
    return button.View()
})
  1. Use computed for derived views:
// ✅ Good: Cached computed
filtered := bubbly.NewComputed(func() []Item {
    return filterItems(allItems.Get())
})

// In template
items := filtered.Get()  // Fast if no change

// ❌ Bad: Filter every render
Template(func(ctx bubbly.RenderContext) string {
    items := filterItems(allItems.Get())  // Runs every render
})
  1. Limit table/list rendering:
// ✅ Good: Virtual scrolling for large lists
table := components.Table(components.TableProps{
    Rows: sliceRows(data, 0, 50), // Only render visible
})

// ❌ Bad: Render all rows
rows := [][]string{}
for i := 0; i < 10000; i++ {  // Too many!
    rows = append(rows, row)
}
  1. Batch component initialization:
// ✅ Good: Init multiple at once
components := []bubbly.Component{
    components.Button(props1),
    components.Button(props2),
    components.Button(props3),
}
for _, c := range components {
    c.Init()  // Batch init
}

// ❌ Bad: Init individually
btn1 := components.Button(props1)
btn1.Init()
btn2 := components.Button(props2)
btn2.Init()  // More function calls
  1. Use simple components for static content:
// ✅ Good: Text component for static text
label := components.Text(components.TextProps{
    Content: "Enter username:",
})

// ❌ Bad: Overkill components
label := components.Card(components.CardProps{
    Content: "Enter username:",
})  // Card has borders, padding, etc.

🧪 Testing

Test Coverage
# Run component tests
go test -race -cover ./pkg/components/...

# Coverage report
go test -coverprofile=coverage.out ./pkg/components/...
go tool cover -html=coverage.out

Coverage: ~88% (as of v3.0)

Testing Components
import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/newbpydev/bubblyui/pkg/components"
)

func TestButton(t *testing.T) {
    clicked := false
    
    button := components.Button(components.ButtonProps{
        Label:   "Test",
        Variant: components.ButtonPrimary,
        OnClick: func() {
            clicked = true
        },
    })
    button.Init()
    
    // Verify render
    output := button.View()
    assert.Contains(t, output, "Test")
    
    // Verify interaction
    // (Components don't have direct interaction testing - test via parent)
}

func TestInput(t *testing.T) {
    value := bubbly.NewRef("")
    changed := ""
    
    input := components.Input(components.InputProps{
        Value:    value,
        OnChange: func(v string) {
            changed = v
        },
    })
    input.Init()
    
    // Verify render
    output := input.View()
    assert.Contains(t, output, "╭")  // Has border
    
    // Test value binding
    value.Set("test")
    // In real app: Simulate typing and verify OnChange called
}

func TestFormSubmission(t *testing.T) {
    submitted := false
    
    nameRef := bubbly.NewRef("Alice")
    
    form := components.Form(components.FormProps[UserData]{
        Initial: UserData{Name: "Alice"},
        Fields: []components.FormField{
            {
                Name:  "name",
                Label: "Name",
                Component: components.Input(components.InputProps{
                    Value: nameRef,
                }),
            },
        },
        OnSubmit: func(data UserData) error {
            submitted = true
            assert.Equal(t, "Alice", data.Name)
            return nil
        },
    })
    form.Init()
    
    // Verify form renders
    output := form.View()
    assert.Contains(t, output, "Name")
    assert.Contains(t, output, "Submit")
    
    // Submit form (in integration test via parent component)
}

func TestTableRendering(t *testing.T) {
    table := components.Table(components.TableProps{
        Headers: []string{"Name", "Age"},
        Rows: [][]string{
            {"Alice", "30"},
            {"Bob", "25"},
        },
    })
    table.Init()
    
    output := table.View()
    
    // Verify headers
    assert.Contains(t, output, "Name")
    assert.Contains(t, output, "Age")
    
    // Verify rows
    assert.Contains(t, output, "Alice")
    assert.Contains(t, output, "Bob")
    
    // Verify table structure
    assert.Contains(t, output, "╭") // Has border
    assert.Contains(t, output, "├") // Has separator
}

🔍 Debugging & Troubleshooting

Common Issues

Issue 1: Component not rendering after state change

// ❌ Wrong: Using component without re-init
var input bubbly.Component

Setup(func(ctx *bubbly.Context) {
    value := bubbly.NewRef("")
    input = components.Input(components.InputProps{
        Value: value,
    })
    input.Init()
    
    ctx.On("update", func(_ interface{}) {
        // Changing ref
        value.Set("new value")
        // But input already initialized with old ref
    })
})

// ✅ Correct: Recreate component or use reactive refs
Setup(func(ctx *bubbly.Context) {
    value := bubbly.NewRef("")
    
    ctx.On("update", func(_ interface{}) {
        value.Set("new value")
        // Input automatically updates via reactive binding
    })
})

Template(func(ctx bubbly.RenderContext) string {
    value := ctx.Get("value").(*bubbly.Ref[string])
    input := components.Input(components.InputProps{
        Value: value,  // Always uses current ref
    })
    input.Init()
    return input.View()
})

Issue 2: Form validation not showing errors

// ❌ Wrong: No validation error display
form := components.Form(components.FormProps[Data]{
    Fields: fields,
    OnSubmit: func(data Data) error {
        if data.Name == "" {
            return errors.New("name required")  // Not displayed
        }
        return nil
    },
})

// ✅ Correct: Return field errors
form := components.Form(components.FormProps[Data]{
    Fields: fields,
    Validate: func(data Data) map[string]string {
        errors := make(map[string]string)
        if data.Name == "" {
            errors["Name"] = "Name is required"
        }
        if data.Email == "" {
            errors["Email"] = "Email is required"
        }
        return errors  // Form displays these
    },
})

Issue 3: Modal not closing

// ❌ Wrong: No visibility control
modal := components.Modal(components.ModalProps{
    Title:   "Alert",
    Content: "This is a modal",
    // Missing Visible ref!
})

// ✅ Correct: Use reactive visibility
showModal := bubbly.NewRef(false)

modal := components.Modal(components.ModalProps{
    Title:   "Alert",
    Content: "This is a modal",
    Visible: showModal,  // REQUIRED
    OnConfirm: func() {
        doAction()
        showModal.Set(false)  // Close modal
    },
    OnCancel: func() {
        showModal.Set(false)  // Close modal
    },
})

Issue 4: Theme not applied

// ❌ Wrong: Not providing theme
theme := components.DefaultTheme
theme.Primary = lipgloss.Color("99")

button := components.Button(components.ButtonProps{
    Label:   "Button",
    Variant: components.ButtonPrimary,
})
button.Init()
// Button uses DefaultTheme, not your modified version

// ✅ Correct: Provide theme via composition
app := bubbly.NewComponent("App").
    Setup(func(ctx *bubbly.Context) {
        theme := components.DefaultTheme
        theme.Primary = lipgloss.Color("99")
        ctx.Provide("theme", theme)  // Provide to component tree
    }).
    Build()

// Components automatically inject theme

📖 Best Practices

Do's ✓
  1. Use atomic design hierarchy:
// ✅ Good: Build from small to large
// Atoms
label := components.Text(...)
input := components.Input(...)

// Molecules
formField := components.FormField{
    Label:     "Name:",
    Component: input,
}

// Organisms
form := components.Form(...)

// Templates
page := components.PageLayout(...)
  1. Bind to refs for reactive updates:
// ✅ Good: Two-way binding
name := bubbly.NewRef("")
input := components.Input(components.InputProps{
    Value: name,  // Bound
})

// ❌ Bad: Static value
input := components.Input(components.InputProps{
    Value: bubbly.NewRef("static"),  // Won't update
})
  1. Always call Init() before View():
// ✅ Correct order
button := components.Button(props)
button.Init()  // Required
view := button.View()

// ❌ Wrong order
button := components.Button(props)
view := button.View()  // May panic or render incorrectly
button.Init()          // Too late
  1. Use theme consistently:
// ✅ Good: Use theme colors
theme := components.DefaultTheme
style := lipgloss.NewStyle().
    Background(theme.Background).
    BorderForeground(theme.BorderColor)

// ❌ Bad: Hardcoded colors
style := lipgloss.NewStyle().
    Background(lipgloss.Color("236")).
    BorderForeground(lipgloss.Color("240"))
  1. Handle validation properly:
// ✅ Good: Validate on blur/submit
input := components.Input(components.InputProps{
    Value: valueRef,
    Validate: func(s string) error {
        if s == "" {
            return errors.New("required")
        }
        return nil
    },
    OnBlur: func() {
        // Validate when user leaves field
        validateField()
    },
})

// ❌ Bad: Validate on every keystroke (annoying)
input := components.Input(components.InputProps{
    OnChange: func(value string) {
        if value == "" {
            showError("Required")  // Too aggressive
        }
    },
})
Don'ts ✗
  1. Don't create components in render loop:
// ❌ Bad: Creates new component every render
Template(func(ctx bubbly.RenderContext) string {
    button := components.Button(props)  // New allocation
    button.Init()                       // Init every time
    return button.View()
})

// ✅ Good: Create once, reuse
Setup(func(ctx *bubbly.Context) {
    button := components.Button(props)
    button.Init()
    ctx.Expose("button", button)
})

Template(func(ctx bubbly.RenderContext) string {
    return ctx.Get("button").(bubbly.Component).View()
})
  1. Don't modify ref types:
// ❌ Type mismatch
nameRef := bubbly.NewRef("Alice")  // string
input := components.Input(components.InputProps{
    Value: nameRef,  // ✅ Works
})

changedRef := bubbly.NewRef(0)  // int
input := components.Input(components.InputProps{
    Value: changedRef,  // ❌ Wrong type!
})
  1. Don't forget error handling:
// ❌ Bad: Ignores errors
comp, _ := components.Button(props)  // What if error?

// ✅ Good: Handle errors
comp, err := components.Button(props)
if err != nil {
    log.Printf("Failed to create button: %v", err)
    return fallbackContent
}
  1. Don't overuse modals:
// ❌ Bad: Modal for simple confirmation
func deleteItem() {
    showModal.Set(true)  // Overkill
}

// ✅ Better: In-line confirmation
button := components.Button(components.ButtonProps{
    Label:   "Delete",
    Variant: components.ButtonDanger,
    OnClick: func() {
        if confirm("Really delete?") {
            delete()
        }
    },
})
  1. Don't hardcode component IDs:
// ❌ Bad: Static ID
button := components.Button(components.ButtonProps{
    CommonProps: components.CommonProps{
        ID: "submit-btn",  // Same for all instances
    },
})

// ✅ Better: Dynamic ID
var idCounter int
func createButton() {
    idCounter++
    button := components.Button(components.ButtonProps{
        CommonProps: components.CommonProps{
            ID: components.ComponentID(fmt.Sprintf("btn-%d", idCounter)),
        },
    })
}

📚 Complete Examples

Example 1: Login Screen
package main

import (
    "fmt"
    tea "github.com/charmbracelet/bubbletea"
    "github.com/charmbracelet/lipgloss"
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
)

func CreateLoginScreen() (bubbly.Component, error) {
    // Reactive state
    username := bubbly.NewRef("")
    password := bubbly.NewRef("")
    errorMsg := bubbly.NewRef("")
    
    return bubbly.NewComponent("LoginScreen").
        WithAutoCommands(true).
        Setup(func(ctx *bubbly.Context) {
            ctx.Expose("username", username)
            ctx.Expose("password", password)
            ctx.Expose("errorMsg", errorMsg)
            
            ctx.On("login", func(_ interface{}) {
                if username.Get() == "" || password.Get() == "" {
                    errorMsg.Set("Username and password required")
                    return
                }
                
                // Simulate login
                if username.Get() == "admin" && password.Get() == "secret" {
                    fmt.Println("Login successful!")
                    // Navigate to dashboard
                } else {
                    errorMsg.Set("Invalid credentials")
                }
            })
            
            ctx.On("clearError", func(_ interface{}) {
                errorMsg.Set("")
            })
        }).
        Template(func(ctx bubbly.RenderContext) string {
            u := ctx.Get("username").(*bubbly.Ref[string]).Get()
            p := ctx.Get("password").(*bubbly.Ref[string]).Get()
            err := ctx.Get("errorMsg").(*bubbly.Ref[string]).Get()
            
            // Create UI components
            title := components.Text(components.TextProps{
                Content: "Login",
                Bold:    true,
                Color:   lipgloss.Color("99"),
            })
            title.Init()
            
            if u == "" && p == "" {
                ctx.Emit("clearError", nil)
            }
            
            userInput := components.Input(components.InputProps{
                Value:       bubbly.NewRef(u),
                Placeholder: "Username",
                Width:       30,
                OnChange: func(value string) {
                    username.Set(value)
                },
            })
            userInput.Init()
            
            passInput := components.Input(components.InputProps{
                Value:       bubbly.NewRef(p),
                Placeholder: "Password",
                Type:        components.InputPassword,
                Width:       30,
                OnChange: func(value string) {
                    password.Set(value)
                },
            })
            passInput.Init()
            
            loginBtn := components.Button(components.ButtonProps{
                Label:   "Login",
                Variant: components.ButtonPrimary,
                OnClick: func() {
                    ctx.Emit("login", nil)
                },
            })
            loginBtn.Init()
            
            errorText := components.Text(components.TextProps{
                Content: err,
                Color:   lipgloss.Color("196"),
            })
            errorText.Init()
            
            var errorSection string
            if err != "" {
                errorSection = lipgloss.NewStyle().
                    Padding(1).
                    Render(errorText.View())
            }
            
            return lipgloss.NewStyle().
                Padding(2).
                Render(lipgloss.JoinVertical(
                    lipgloss.Center,
                    title.View(),
                    "",
                    userInput.View(),
                    passInput.View(),
                    "",
                    loginBtn.View(),
                    "",
                    errorSection,
                ))
        }).
        WithKeyBinding("enter", "login", "Submit login").
        Build()
}

func main() {
    app, err := CreateLoginScreen()
    if err != nil {
        panic(err)
    }
    
    p := tea.NewProgram(bubbly.Wrap(app), tea.WithAltScreen())
    if _, err := p.Run(); err != nil {
        panic(err)
    }
}
Example 2: Data Dashboard
func CreateDashboard() (bubbly.Component, error) {
    return bubbly.NewComponent("Dashboard").
        WithAutoCommands(true).
        Setup(func(ctx *bubbly.Context) {
            // State
            users := bubbly.NewRef([]User{})
            orders := bubbly.NewRef([]Order{})
            loading := bubbly.NewRef(true)
            
            // Load data
            ctx.OnMounted(func() {
                loadDashboardData(users, orders, loading)
            })
            
            // Auto-refresh
            ticker := time.NewTicker(30 * time.Second)
            go func() {
                for range ticker.C {
                    refreshDashboard(users, orders)
                }
            }()
            
            ctx.Set("ticker", ticker)
            ctx.Expose("users", users)
            ctx.Expose("orders", orders)
            ctx.Expose("loading", loading)
        }).
        Template(func(ctx bubbly.RenderContext) string {
            if ctx.Get("loading").(*bubbly.Ref[bool]).Get() {
                spinner := components.Spinner(components.SpinnerProps{
                    Message: "Loading dashboard...",
                })
                spinner.Init()
                return spinner.View()
            }
            
            // Create metric cards
            userCount := len(ctx.Get("users").(*bubbly.Ref[[]User]).Get())
            orderCount := len(ctx.Get("orders").(*bubbly.Ref[[]Order]).Get())
            
            userCard := components.Card(components.CardProps{
                Title:       "Total Users",
                Content:     fmt.Sprintf("%d", userCount),
                Padding:     2,
                Width:       20,
            })
            userCard.Init()
            
            orderCard := components.Card(components.CardProps{
                Title:       "Orders Today",
                Content:     fmt.Sprintf("%d", orderCount),
                Padding:     2,
                Width:       20,
            })
            orderCard.Init()
            
            // Stats grid
            statsGrid := components.GridLayout(components.GridLayoutProps{
                Columns: 2,
                Rows:    1,
                Cells:   []bubbly.Component{userCard, orderCard},
            })
            statsGrid.Init()
            
            // Recent orders table
            orders := ctx.Get("orders").(*bubbly.Ref[[]Order]).Get()
            rows := [][]string{}
            for _, order := range orders {
                rows = append(rows, []string{
                    fmt.Sprintf("#%d", order.ID),
                    order.Customer,
                    fmt.Sprintf("$%.2f", order.Total),
                    order.Status,
                })
            }
            
            ordersTable := components.Table(components.TableProps{
                Headers: []string{"Order", "Customer", "Total", "Status"},
                Rows:    rows,
                Width:   80,
            })
            ordersTable.Init()
            
            return components.AppLayout(components.AppLayoutProps{
                Header:  components.Text(components.TextProps{
                    Content: "Dashboard",
                    Bold:    true,
                    Color:   lipgloss.Color("99"),
                }),
                Main:    lipgloss.JoinVertical(
                    lipgloss.Left,
                    statsGrid.View(),
                    "",
                    "Recent Orders:",
                    ordersTable.View(),
                ),
            }).View()
        }).
        Build()
}
More Examples

See cmd/examples/ for:

  • Todo App - Complete CRUD with form validation
  • CRM Dashboard - Tables, stats, user management
  • Settings Panel - Tabs, forms, toggles
  • E-commerce UI - Product grid, cart, checkout
  • DevTools Demo - All components showcase
  • Admin Panel - Data tables, modals, layouts

🎯 Use Cases

Use Case 1: Admin Panel

Scenario: Backend administration interface with user management, settings, analytics

Components Used:

  • Form (user creation/edit)
  • Table (user listing with pagination)
  • Modal (delete confirmation)
  • Tabs (different sections)
  • Toggle (feature flags)
  • AppLayout (overall structure)

Why this package? Complete component suite ready for production use

// See Dashboard example above
// + Tabs for Users, Settings, Analytics
// + Modal for confirmations
// + Form for user creation
Use Case 2: CLI Tool with Interactive Menus

Scenario: Command-line tool with interactive configuration wizard

Components Used:

  • List (menu navigation)
  • Input (configuration values)
  • Select (option selection)
  • Checkbox (feature toggles)
  • Radio (mode selection)
  • Button (actions)

Why this package? Brings GUI-like experience to terminal

// Menu-driven config
menu := components.List(components.ListProps{
    Items: []string{
        "1. Configure Database",
        "2. Set API Keys",
        "3. Configure Logging",
        "4. Save & Exit",
    },
})
Use Case 3: Monitoring Dashboard

Scenario: Real-time system monitoring with charts, alerts, metrics

Components Used:

  • Card (metric displays)
  • Table (log table)
  • GridLayout (dashboard layout)
  • Spinner (loading states)
  • Text (status messages)
  • AppLayout (header/dashboard/footer)

Why this package? Real-time updates with reactive state

// Auto-updating metrics
totalRequests := bubbly.NewRef(0)
errorCount := bubbly.NewRef(0)

// Computed: error rate
errorRate := bubbly.NewComputed(func() float64 {
    total := totalRequests.Get()
    if total == 0 {
        return 0
    }
    return float64(errorCount.Get()) / float64(total) * 100
})

// Cards update automatically
errorRateCard := components.Card(components.CardProps{
    Title:   "Error Rate",
    Content: fmt.Sprintf("%.2f%%", errorRate.Get()),
})

🔗 API Reference

See Full Components API Reference for:

  • Complete props for all 24 components
  • Event callbacks and handlers
  • Style customization options
  • Accessibility attributes
  • Keyboard shortcuts
  • Virtual scrolling API
  • Advanced form validation
  • Custom component patterns

🤝 Contributing

See CONTRIBUTING.md for:

  • Adding new components
  • Component design guidelines
  • Theming best practices
  • A11y requirements
  • Testing requirements
  • Documentation standards

📄 License

MIT License - See LICENSE for details.


✅ Package Documentation Status

Package: pkg/components
Status: ✅ Complete
Lines: 5,887 (production code)
Files: 27 component files
Coverage: 88% test coverage
Updated: November 18, 2025

Documentation includes:

  • Package purpose and atomic design overview
  • Quick start with component usage
  • Architecture: 4 core concepts (atomic design, theming, reactive binding, common props)
  • Package structure (24 components organized)
  • 24 features (5 atoms + 6 molecules + 9 organisms + 4 templates) with full API + examples
  • Integration with 4 other packages
  • Performance benchmarks
  • Testing with examples
  • Debugging 4 common issues
  • Best practices (5 do's, 5 don'ts)
  • 2 complete working examples
  • 3 detailed use cases
  • API reference link

Components documented:

  • 5 Atoms: Button, Text, Icon, Badge, Spinner, Spacer
  • 6 Molecules: Input, Checkbox, Select, TextArea, Toggle, Radio
  • 9 Organisms: Form, Table, List, Card, Modal, Tabs, Accordion, Menu
  • 4 Templates: AppLayout, PageLayout, GridLayout, PanelLayout

Next Package: pkg/bubbly/composables - Vue-style composables


📋 Package README Completion Status

Package Status Size Files Coverage
pkg/bubbly ✅ Complete 32,595 LOC 27 85%
pkg/components ✅ Complete 5,887 LOC 27 88%
pkg/bubbly/composables 🔄 Pending - - -
pkg/bubbly/directives ⏳ Pending - - -
pkg/bubbly/router ⏳ Pending - - -
pkg/bubbly/devtools ⏳ Pending - - -
pkg/bubbly/observability ⏳ Pending - - -
pkg/bubbly/monitoring ⏳ Pending - - -

Progress: 2/8 packages complete (25%)

Documentation

Overview

Package components provides layout components for the BubblyUI framework.

Package components provides layout components for the BubblyUI framework.

Package components provides layout components for the BubblyUI framework.

Package components provides layout components for the BubblyUI framework.

Package components provides a comprehensive library of production-ready TUI components following atomic design principles.

Overview

The components package offers a complete set of pre-built, type-safe, and well-tested TUI components that leverage all BubblyUI framework features (reactivity, lifecycle, composition API, directives) to provide a consistent foundation for building terminal applications.

Atomic Design Hierarchy

Components are organized into four levels following atomic design principles:

  • Atoms: Basic building blocks (Button, Text, Icon, Spacer, Badge, Spinner)
  • Molecules: Simple combinations (Input, Checkbox, Select, TextArea, Radio, Toggle)
  • Organisms: Complex features (Form, Table, List, Modal, Card, Menu, Tabs, Accordion)
  • Templates: Layout structures (AppLayout, PageLayout, PanelLayout, GridLayout)

Quick Start

Import the components package:

import (
    "github.com/newbpydev/bubblyui/pkg/bubbly"
    "github.com/newbpydev/bubblyui/pkg/components"
)

Use built-in components:

button := components.Button(components.ButtonProps{
    Label:   "Submit",
    Variant: components.ButtonPrimary,
    OnClick: func() {
        handleSubmit()
    },
})

Theming

All components use a consistent theming system based on Lipgloss:

theme := components.DefaultTheme
// Customize theme colors
theme.Primary = lipgloss.Color("63")

Provide theme to your application:

Setup(func(ctx *bubbly.Context) {
    ctx.Provide("theme", theme)
})

Components automatically inject and use the provided theme.

Type Safety

All components use Go generics for type-safe props and state:

// Type-safe form with generic data type
form := components.Form(components.FormProps[UserData]{
    Initial:  UserData{},
    Validate: validateUser,
    OnSubmit: saveUser,
})

// Type-safe table with generic row type
table := components.Table(components.TableProps[User]{
    Data:    usersRef,
    Columns: userColumns,
})

Component Composition

Components compose naturally to build complex UIs:

// Compose atoms into molecules
input := components.Input(components.InputProps{
    Value:       nameRef,
    Placeholder: "Enter name",
})

// Compose molecules into organisms
form := components.Form(components.FormProps[Data]{
    Fields: []components.FormField{
        {Name: "name", Label: "Name", Component: input},
    },
})

// Compose organisms into templates
app := components.AppLayout(components.AppLayoutProps{
    Header:  headerComponent,
    Content: form,
})

Reactivity Integration

Components integrate seamlessly with BubblyUI's reactivity system:

// Create reactive state
value := bubbly.NewRef("")

// Bind to input component
input := components.Input(components.InputProps{
    Value: value, // Two-way binding
})

// Watch for changes
bubbly.Watch(value, func(newVal, oldVal string) {
    fmt.Printf("Value changed: %s\n", newVal)
})

Event Handling

Components emit events for user interactions:

button := components.Button(components.ButtonProps{
    Label: "Click me",
    OnClick: func() {
        // Handle click event
    },
})

input := components.Input(components.InputProps{
    OnChange: func(value string) {
        // Handle value change
    },
    OnBlur: func() {
        // Handle blur event
    },
})

Validation

Input components support validation:

input := components.Input(components.InputProps{
    Value: emailRef,
    Validate: func(value string) error {
        if !strings.Contains(value, "@") {
            return errors.New("invalid email")
        }
        return nil
    },
})

Form components aggregate validation:

form := components.Form(components.FormProps[UserData]{
    Validate: func(data UserData) map[string]string {
        errors := make(map[string]string)
        if data.Email == "" {
            errors["email"] = "Email is required"
        }
        return errors
    },
})

Accessibility

All components follow TUI accessibility best practices:

  • Keyboard navigation for all interactive components
  • Focus indicators visible with distinct styling
  • Screen reader hints where applicable
  • Semantic structure with clear visual hierarchy
  • High contrast color schemes

Performance

Components are optimized for terminal rendering:

  • Button: < 1ms render time
  • Input: < 2ms render time
  • Form: < 10ms render time
  • Table (100 rows): < 50ms render time
  • List (1000 items): < 100ms with virtual scrolling

Examples

See the examples directory for complete applications:

  • Todo app: Form and List composition
  • Dashboard: Table and Card layout
  • Settings: Tabs and Form integration
  • Data browser: Table with Modal

Package Structure

The package is organized by atomic design level:

  • doc.go: Package documentation
  • types.go: Common types and interfaces
  • theme.go: Theming system
  • button.go, text.go, icon.go: Atom components
  • input.go, checkbox.go, select.go: Molecule components
  • form.go, table.go, list.go: Organism components
  • app_layout.go, page_layout.go: Template components

Design Philosophy

Components follow these principles:

  • Type Safety: Leverage Go generics for compile-time checking
  • Consistency: Unified styling and behavior across all components
  • Composability: Build complex UIs from simple building blocks
  • Accessibility: Usable by everyone, keyboard-first design
  • Performance: Optimized for terminal rendering
  • Integration: Seamless integration with BubblyUI framework

Compatibility

  • Requires Go 1.22+ (generics)
  • Requires BubblyUI framework (features 01-05)
  • Uses Lipgloss for styling
  • Compatible with Bubbletea v1.0+

License

See the LICENSE file in the repository root.

Package components provides layout components for the BubblyUI framework.

Package components provides layout components for the BubblyUI framework.

Package components provides layout type constants for the BubblyUI advanced layout system.

Package components provides layout components for the BubblyUI framework.

Index

Constants

View Source
const (
	CheckboxUnchecked = "☐" // Unchecked box (U+2610)
	CheckboxChecked   = "☑" // Checked box (U+2611)
)

Checkbox indicator constants

View Source
const (
	// DefaultHorizontalChar is the default character for horizontal dividers.
	DefaultHorizontalChar = "─"

	// DefaultVerticalChar is the default character for vertical dividers.
	DefaultVerticalChar = "│"

	// DefaultDividerLength is the default length when not specified.
	DefaultDividerLength = 20
)

Default divider characters for horizontal and vertical orientations.

View Source
const DefaultHStackDividerChar = "│"

DefaultHStackDividerChar is the default divider character for HStack. Uses vertical line since HStack renders horizontally.

View Source
const DefaultStackSpacing = 1

DefaultStackSpacing is the default spacing between items in a stack.

View Source
const DefaultVStackDividerChar = "─"

DefaultVStackDividerChar is the default divider character for VStack. Uses horizontal line since VStack renders vertically.

Variables

View Source
var DarkTheme = Theme{
	Primary:     lipgloss.Color("75"),
	Secondary:   lipgloss.Color("245"),
	Success:     lipgloss.Color("82"),
	Warning:     lipgloss.Color("220"),
	Danger:      lipgloss.Color("203"),
	Info:        lipgloss.Color("51"),
	Background:  lipgloss.Color("234"),
	Foreground:  lipgloss.Color("255"),
	Muted:       lipgloss.Color("243"),
	Border:      lipgloss.RoundedBorder(),
	BorderColor: lipgloss.Color("243"),
	Padding:     1,
	Margin:      1,
	Radius:      1,
}

DarkTheme provides a theme optimized for dark terminal backgrounds. It uses higher contrast colors for better visibility on dark backgrounds.

View Source
var DefaultTheme = Theme{
	Primary:     lipgloss.Color("75"),
	Secondary:   lipgloss.Color("240"),
	Success:     lipgloss.Color("46"),
	Warning:     lipgloss.Color("226"),
	Danger:      lipgloss.Color("196"),
	Info:        lipgloss.Color("39"),
	Background:  lipgloss.Color("235"),
	Foreground:  lipgloss.Color("255"),
	Muted:       lipgloss.Color("240"),
	Border:      lipgloss.RoundedBorder(),
	BorderColor: lipgloss.Color("240"),
	Padding:     1,
	Margin:      1,
	Radius:      1,
}

DefaultTheme provides a carefully crafted default color scheme optimized for terminal readability and accessibility.

Color choices:

  • Primary (75): Bright blue for main actions (>3:1 contrast)
  • Secondary (240): Neutral gray for alternatives
  • Success (46): Bright green for positive feedback
  • Warning (226): Yellow for caution
  • Danger (196): Red for errors and destructive actions
  • Info (39): Cyan for informational content
  • Background (235): Dark gray for backgrounds
  • Foreground (255): White for primary text
  • Muted (240): Gray for secondary text

This theme works well in both light and dark terminal backgrounds with sufficient contrast for accessibility (WCAG AA compliant).

View Source
var HighContrastTheme = Theme{
	Primary:     lipgloss.Color("15"),
	Secondary:   lipgloss.Color("250"),
	Success:     lipgloss.Color("10"),
	Warning:     lipgloss.Color("11"),
	Danger:      lipgloss.Color("9"),
	Info:        lipgloss.Color("14"),
	Background:  lipgloss.Color("0"),
	Foreground:  lipgloss.Color("15"),
	Muted:       lipgloss.Color("7"),
	Border:      lipgloss.NormalBorder(),
	BorderColor: lipgloss.Color("15"),
	Padding:     1,
	Margin:      1,
	Radius:      0,
}

HighContrastTheme provides maximum contrast for accessibility. Ideal for users with visual impairments or in bright environments.

View Source
var LightTheme = Theme{
	Primary:     lipgloss.Color("27"),
	Secondary:   lipgloss.Color("240"),
	Success:     lipgloss.Color("28"),
	Warning:     lipgloss.Color("136"),
	Danger:      lipgloss.Color("160"),
	Info:        lipgloss.Color("31"),
	Background:  lipgloss.Color("255"),
	Foreground:  lipgloss.Color("235"),
	Muted:       lipgloss.Color("245"),
	Border:      lipgloss.RoundedBorder(),
	BorderColor: lipgloss.Color("245"),
	Padding:     1,
	Margin:      1,
	Radius:      1,
}

LightTheme provides a theme optimized for light terminal backgrounds. It uses darker colors for better visibility on light backgrounds.

Functions

func Accordion

func Accordion(props AccordionProps) bubbly.Component

Accordion creates an accordion collapsible panels component. The accordion displays a list of panels that can be expanded/collapsed.

Features:

  • Multiple collapsible panels
  • Expand/collapse functionality
  • Single or multiple expansion modes
  • Reactive expanded state
  • OnToggle callback
  • String or Component content
  • Theme integration
  • Custom style override

Example:

expanded := bubbly.NewRef([]int{0})
accordion := Accordion(AccordionProps{
    Items: []AccordionItem{
        {Title: "Section 1", Content: "Content for section 1"},
        {Title: "Section 2", Content: "Content for section 2"},
        {Title: "Section 3", Content: "Content for section 3"},
    },
    ExpandedIndexes: expanded,
    AllowMultiple:   true,
})

func AppLayout

func AppLayout(props AppLayoutProps) bubbly.Component

AppLayout creates a full application layout template component. The layout positions Header, Sidebar, Content, and Footer sections using Lipgloss layout functions.

Layout Structure:

┌─────────────────────────────────┐
│          Header (full width)    │
├──────────┬──────────────────────┤
│ Sidebar  │      Content         │
│          │                      │
├──────────┴──────────────────────┤
│          Footer (full width)    │
└─────────────────────────────────┘

Features:

  • Full application layout with four sections
  • Responsive to terminal size
  • Configurable dimensions for each section
  • Theme integration for consistent styling
  • Custom style override support
  • Lipgloss-based layout positioning

Example:

layout := AppLayout(AppLayoutProps{
    Header:  Text(TextProps{Content: "My App"}),
    Sidebar: Menu(MenuProps{Items: menuItems}),
    Content: Card(CardProps{Title: "Dashboard"}),
    Footer:  Text(TextProps{Content: "© 2024"}),
})

func Badge

func Badge(props BadgeProps) bubbly.Component

Badge creates a new Badge atom component.

Badge is a small status indicator component that displays short text labels with colored backgrounds. Commonly used for status indicators, counts, labels, and notifications.

The badge component automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

badge := components.Badge(components.BadgeProps{
    Label:   "Active",
    Variant: components.VariantSuccess,
})

// Initialize and use with Bubbletea
badge.Init()
view := badge.View()

Common use cases:

  • Status indicators (Active, Inactive, Pending)
  • Notification counts (5 new messages)
  • Category labels (Bug, Feature, Documentation)
  • Priority markers (High, Medium, Low)

Accessibility:

  • Clear visual distinction with variant colors
  • High contrast for readability
  • Compact design for inline use

func Box

func Box(props BoxProps) bubbly.Component

Box creates a generic container component. The box provides a flexible container with optional padding, border, title, and background color. It can contain either a child component or text content.

Features:

  • Optional child component or text content
  • Configurable padding (uniform or per-axis)
  • Optional border with customizable style
  • Optional title header
  • Fixed or auto dimensions
  • Background color support
  • Theme integration for consistent styling
  • Custom style override support

Example:

box := Box(BoxProps{
    Content: "Hello, World!",
    Padding: 1,
    Border:  true,
    Title:   "Greeting",
})

// With child component
box := Box(BoxProps{
    Child:   myComponent,
    Border:  true,
    Width:   40,
})

func Button

func Button(props ButtonProps) bubbly.Component

Button creates a new Button atom component.

Button is a fundamental interactive element that triggers actions when clicked. It supports multiple visual variants, disabled states, and custom click handlers.

The button automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

button := components.Button(components.ButtonProps{
    Label:   "Save Changes",
    Variant: components.ButtonPrimary,
    OnClick: func() {
        fmt.Println("Saving...")
    },
})

// Initialize and use with Bubbletea
button.Init()
view := button.View()

Keyboard interaction:

  • Enter/Space: Trigger click event (when focused)

Accessibility:

  • Clear visual distinction between enabled/disabled states
  • Keyboard accessible
  • High contrast variants available via theme

func Card

func Card(props CardProps) bubbly.Component

Card creates a card container component. The card displays a bordered box with optional title, content, footer, and child components.

Features:

  • Optional title header
  • Content text or child components
  • Optional footer
  • Configurable width, height, and padding
  • Border can be toggled on/off
  • Theme integration for consistent styling
  • Custom style override support

Example:

card := Card(CardProps{
    Title:   "User Profile",
    Content: "Name: John Doe\nEmail: john@example.com",
    Footer:  "Last updated: 2024-01-01",
    Width:   50,
})

func Center

func Center(props CenterProps) bubbly.Component

Center creates a centering layout component. The component centers its child horizontally and/or vertically within a container of specified dimensions.

Features:

  • Centers content horizontally within container
  • Centers content vertically within container
  • Centers both directions by default
  • Supports fixed or auto dimensions
  • Theme integration for consistent styling
  • Custom style override support

Centering Behavior:

  • When neither Horizontal nor Vertical is set: centers both directions (default)
  • When Horizontal=true: centers only horizontally
  • When Vertical=true: centers only vertically
  • When both are true: centers both directions

Dimension Requirements:

  • Horizontal centering requires Width > 0
  • Vertical centering requires Height > 0
  • Auto-sizing (0) uses content dimensions

Example:

// Center both directions (default)
center := Center(CenterProps{
    Child:  myComponent,
    Width:  80,
    Height: 24,
})

// Center horizontally only
center := Center(CenterProps{
    Child:      myComponent,
    Width:      80,
    Horizontal: true,
})

// Center vertically only
center := Center(CenterProps{
    Child:    myComponent,
    Height:   24,
    Vertical: true,
})

// Modal centering pattern
modal := Center(CenterProps{
    Child: Card(CardProps{
        Title:   "Confirm",
        Content: "Are you sure?",
    }),
    Width:  80,
    Height: 24,
})

func Checkbox

func Checkbox(props CheckboxProps) bubbly.Component

Checkbox creates a new Checkbox molecule component.

Checkbox is an interactive toggle element that allows users to select or deselect an option. It supports reactive state binding, callbacks, and disabled states.

The checkbox automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

checkedRef := bubbly.NewRef(false)
checkbox := components.Checkbox(components.CheckboxProps{
    Label:   "Enable notifications",
    Checked: checkedRef,
    OnChange: func(checked bool) {
        if checked {
            enableNotifications()
        } else {
            disableNotifications()
        }
    },
})

// Initialize and use with Bubbletea
checkbox.Init()
view := checkbox.View()

Features:

  • Reactive checked state binding with Ref[bool]
  • Toggle functionality via "toggle" event
  • OnChange callback support
  • Disabled state support
  • Label display
  • Theme integration
  • Custom style override

Keyboard interaction:

  • Space/Enter: Toggle checkbox (when focused)

Visual indicators:

  • Unchecked: ☐ (or [ ])
  • Checked: ☑ (or [x])

Accessibility:

  • Clear visual distinction between checked/unchecked states
  • Disabled state clearly indicated
  • Keyboard accessible

func Container

func Container(props ContainerProps) bubbly.Component

Container creates a width-constrained container component. The component limits content width to improve readability and optionally centers the content horizontally.

Features:

  • Preset sizes for common widths (sm=40, md=60, lg=80, xl=100)
  • Custom max-width override
  • Horizontal centering (enabled by default)
  • Full-width mode (no constraint)
  • Theme integration for consistent styling
  • Custom style override support

Width Behavior:

  • Size preset determines default width
  • MaxWidth > 0 overrides Size preset
  • ContainerFull disables width constraint

Centering Behavior:

  • Centered=true (default): content is horizontally centered
  • Centered=false: content is left-aligned

Example:

// Default container (60 chars, centered)
container := Container(ContainerProps{
    Child: myContent,
})

// Large container, not centered
container := Container(ContainerProps{
    Child:    myContent,
    Size:     ContainerLg,
    Centered: false,
})

// Custom width
container := Container(ContainerProps{
    Child:    myContent,
    MaxWidth: 50,
})

// Full width (no constraint)
container := Container(ContainerProps{
    Child: myContent,
    Size:  ContainerFull,
})

// Readable content layout pattern
page := VStack(StackProps{
    Items: []bubbly.Component{
        header,
        Container(ContainerProps{
            Child: article,
            Size:  ContainerLg,
        }),
        footer,
    },
})

func Divider

func Divider(props DividerProps) bubbly.Component

Divider creates a separator line component. The divider can be horizontal or vertical, with an optional centered label.

Features:

  • Horizontal or vertical orientation
  • Configurable length
  • Optional centered label text
  • Customizable divider character
  • Theme integration (uses theme.Muted for color)
  • Custom style override support

Example:

// Simple horizontal divider
divider := Divider(DividerProps{
    Length: 40,
})

// Divider with label
divider := Divider(DividerProps{
    Label:  "OR",
    Length: 30,
})

// Vertical divider
divider := Divider(DividerProps{
    Vertical: true,
    Length:   10,
})

// Custom character
divider := Divider(DividerProps{
    Char:   "═",
    Length: 40,
})

func Flex

func Flex(props FlexProps) bubbly.Component

Flex creates a flexbox-style layout component. Items are arranged in a row or column with configurable alignment and spacing.

Features:

  • Row or column direction
  • Main-axis alignment (justify): start, center, end, space-between, space-around, space-evenly
  • Cross-axis alignment (align): start, center, end, stretch
  • Configurable gap between items
  • Fixed or auto dimensions
  • Theme integration
  • Custom style override support

Example:

// Toolbar with space-between
toolbar := Flex(FlexProps{
    Items: []bubbly.Component{
        Text(TextProps{Content: "Title"}),
        Spacer(SpacerProps{Flex: true}),
        Button(ButtonProps{Label: "Action"}),
    },
    Justify: JustifySpaceBetween,
    Width:   80,
})

// Centered card grid
grid := Flex(FlexProps{
    Items:     cards,
    Direction: FlexRow,
    Justify:   JustifyCenter,
    Gap:       2,
})

// Vertical form layout
form := Flex(FlexProps{
    Items:     []bubbly.Component{input1, input2, submitBtn},
    Direction: FlexColumn,
    Align:     AlignItemsStretch,
    Gap:       1,
})

func Form

func Form[T any](props FormProps[T]) bubbly.Component

Form creates a new Form organism component with generic type support.

Form is a container component that manages multiple input fields, validation, and submission. It integrates with the UseForm composable for state management and provides a consistent layout for forms.

The form automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

type LoginData struct {
    Username string
    Password string
}

usernameRef := bubbly.NewRef("")
passwordRef := bubbly.NewRef("")

form := components.Form(components.FormProps[LoginData]{
    Initial: LoginData{},
    Fields: []components.FormField{
        {
            Name:  "Username",
            Label: "Username",
            Component: components.Input(components.InputProps{
                Value: usernameRef,
            }),
        },
        {
            Name:  "Password",
            Label: "Password",
            Component: components.Input(components.InputProps{
                Value: passwordRef,
                Type: components.InputPassword,
            }),
        },
    },
    Validate: func(data LoginData) map[string]string {
        errors := make(map[string]string)
        if data.Username == "" {
            errors["Username"] = "Username is required"
        }
        if len(data.Password) < 8 {
            errors["Password"] = "Password must be at least 8 characters"
        }
        return errors
    },
    OnSubmit: func(data LoginData) {
        authenticate(data.Username, data.Password)
    },
})

// Initialize and use with Bubbletea
form.Init()
view := form.View()

Features:

  • Generic type support for any form data struct
  • Field collection with labels
  • Validation with error display per field
  • Submit/cancel handlers
  • Integration with UseForm composable
  • Theme integration
  • Custom style override

Keyboard interaction:

  • Tab: Navigate between fields
  • Enter: Submit form (if valid)
  • Escape: Cancel form

The form uses the UseForm composable internally for state management, providing reactive validation and dirty tracking.

func GridLayout

func GridLayout(props GridLayoutProps) bubbly.Component

GridLayout creates a grid-based layout template component. The layout arranges items in a grid with configurable columns, gaps, and cell dimensions.

Layout Structure (3 columns example):

┌─────────┬─────────┬─────────┐
│ Cell 1  │ Cell 2  │ Cell 3  │
├─────────┼─────────┼─────────┤
│ Cell 4  │ Cell 5  │ Cell 6  │
└─────────┴─────────┴─────────┘

Features:

  • Configurable number of columns
  • Adjustable gap between cells
  • Custom cell width and height
  • Automatic row wrapping
  • Theme integration for consistent styling
  • Custom style override support
  • Perfect for dashboards and card grids

Example:

layout := GridLayout(GridLayoutProps{
    Items: []bubbly.Component{
        Card(CardProps{Title: "Card 1"}),
        Card(CardProps{Title: "Card 2"}),
        Card(CardProps{Title: "Card 3"}),
    },
    Columns:   3,
    Gap:       2,
    CellWidth: 25,
})

func HStack

func HStack(props StackProps) bubbly.Component

HStack creates a horizontal stack layout component. Items are arranged horizontally (left to right) with configurable spacing and cross-axis (vertical) alignment.

Features:

  • Horizontal arrangement of child components
  • Configurable spacing between items
  • Cross-axis alignment (start/center/end/stretch)
  • Optional dividers between items
  • Theme integration for divider styling
  • Custom style override support

Example:

// Simple horizontal stack
hstack := HStack(StackProps{
    Items: []interface{}{button1, button2, button3},
    Spacing: 2,
})

// With alignment and dividers
hstack := HStack(StackProps{
    Items:   []interface{}{logo, spacer, menuItems},
    Align:   AlignItemsCenter,
    Divider: true,
})

// Toolbar pattern with flexible spacer
hstack := HStack(StackProps{
    Items: []interface{}{
        Text(TextProps{Content: "Title"}),
        Spacer(SpacerProps{Flex: true}),
        Button(ButtonProps{Label: "Action"}),
    },
})

func Icon

func Icon(props IconProps) bubbly.Component

Icon creates a new Icon atom component.

Icon is a fundamental display element for rendering symbolic glyphs and indicators in the terminal. It supports Unicode characters, emojis, and special symbols with customizable colors and sizes.

The icon component automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

icon := components.Icon(components.IconProps{
    Symbol: "⚠",
    Color:  lipgloss.Color("226"),
    Size:   components.SizeLarge,
})

// Initialize and use with Bubbletea
icon.Init()
view := icon.View()

Common icon symbols:

  • Checkmark: ✓
  • Cross: ✗
  • Warning: ⚠
  • Info: ℹ
  • Star: ★
  • Heart: ♥
  • Arrows: → ← ↑ ↓
  • Shapes: ● ■ ◆

Accessibility:

  • Clear visual distinction with colors
  • High contrast colors available via theme
  • Supports all terminal color profiles

func Input

func Input(props InputProps) bubbly.Component

func List

func List[T any](props ListProps[T]) bubbly.Component
func Menu(props MenuProps) bubbly.Component

Menu creates a menu navigation component. The menu displays a list of selectable items with keyboard navigation support.

Features:

  • List of menu items with labels
  • Selected item highlighting
  • Disabled item support
  • Reactive selection state
  • OnSelect callback
  • Theme integration
  • Custom style override

Example:

selected := bubbly.NewRef("")
menu := Menu(MenuProps{
    Items: []MenuItem{
        {Label: "Home", Value: "home"},
        {Label: "Settings", Value: "settings"},
        {Label: "Logout", Value: "logout"},
    },
    Selected: selected,
    OnSelect: func(value string) {
        navigate(value)
    },
})
func Modal(props ModalProps) bubbly.Component

Modal creates a modal dialog overlay component. The modal displays a centered dialog box with title, content, and optional buttons. It can be shown/hidden by toggling the Visible ref.

Features:

  • Overlay background that dims the content behind
  • Centered dialog box with border
  • Title, content, and optional buttons
  • Keyboard controls: Esc to close, Enter to confirm
  • Theme integration for consistent styling
  • Custom style override support

Example:

visible := bubbly.NewRef(true)
modal := Modal(ModalProps{
    Title:   "Confirm Delete",
    Content: "Are you sure you want to delete this item?",
    Visible: visible,
    OnConfirm: func() {
        // Handle confirmation
        visible.Set(false)
    },
    OnClose: func() {
        visible.Set(false)
    },
})

func PageLayout

func PageLayout(props PageLayoutProps) bubbly.Component

PageLayout creates a simple page structure template component. The layout positions Title, Content, and Actions sections vertically using Lipgloss layout functions.

Layout Structure:

┌─────────────────────────────────┐
│          Title                  │
│                                 │
│          Content                │
│          (main area)            │
│                                 │
│          Actions                │
└─────────────────────────────────┘

Features:

  • Simple vertical page structure with three sections
  • Configurable width and spacing
  • Theme integration for consistent styling
  • Custom style override support
  • Lipgloss-based layout positioning

Example:

layout := PageLayout(PageLayoutProps{
    Title:   Text(TextProps{Content: "Dashboard"}),
    Content: Card(CardProps{Title: "Stats"}),
    Actions: Button(ButtonProps{Label: "Refresh"}),
})

func PanelLayout

func PanelLayout(props PanelLayoutProps) bubbly.Component

PanelLayout creates a split panel layout template component. The layout splits the space either horizontally (left/right) or vertically (top/bottom).

Layout Structure (Horizontal):

┌──────────┬──────────────────────┐
│          │                      │
│   Left   │       Right          │
│          │                      │
└──────────┴──────────────────────┘

Layout Structure (Vertical):

┌─────────────────────────────────┐
│             Top                 │
├─────────────────────────────────┤
│            Bottom               │
└─────────────────────────────────┘

Features:

  • Horizontal or vertical split panels
  • Configurable split ratio
  • Optional borders
  • Theme integration for consistent styling
  • Custom style override support
  • Perfect for master-detail views

Example:

layout := PanelLayout(PanelLayoutProps{
    Left:       List(ListProps{Items: items}),
    Right:      Card(CardProps{Title: "Details"}),
    SplitRatio: 0.3, // 30% left, 70% right
})

func Radio

func Radio[T any](props RadioProps[T]) bubbly.Component

func Select

func Select[T any](props SelectProps[T]) bubbly.Component

func Spacer

func Spacer(props SpacerProps) bubbly.Component

Spacer creates a new Spacer atom component.

Spacer is a layout utility component that creates empty space in the terminal. It can create horizontal space (width), vertical space (height), or both. When Flex=true, the spacer acts as a marker for parent layouts (HStack, VStack, Flex) to fill remaining available space.

Example:

// Fixed horizontal spacer
hSpacer := components.Spacer(components.SpacerProps{
    Width: 10,
})

// Fixed vertical spacer
vSpacer := components.Spacer(components.SpacerProps{
    Height: 3,
})

// Both dimensions
spacer := components.Spacer(components.SpacerProps{
    Width:  20,
    Height: 5,
})

// Flexible spacer - fills available space in parent layout
flexSpacer := components.Spacer(components.SpacerProps{
    Flex: true,
})

// Flexible spacer with minimum width
flexMinSpacer := components.Spacer(components.SpacerProps{
    Flex:  true,
    Width: 5, // minimum 5 characters, parent can expand
})

// Initialize and use with Bubbletea
spacer.Init()
view := spacer.View()

Use cases:

  • Creating margins between components
  • Adding padding in layouts
  • Vertical spacing between sections
  • Horizontal spacing in rows
  • Pushing items to opposite ends (Flex=true in HStack)
  • Creating flexible gaps in toolbars

Accessibility:

  • Invisible but affects layout
  • Helps create visual hierarchy
  • Improves readability

func Spinner

func Spinner(props SpinnerProps) bubbly.Component

Spinner creates a new Spinner atom component.

Spinner is a loading indicator component that shows an animated symbol to indicate background activity or loading states. It can optionally display a label describing the operation.

The spinner component automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

spinner := components.Spinner(components.SpinnerProps{
    Label:  "Loading data...",
    Active: true,
    Color:  lipgloss.Color("99"),
})

// Initialize and use with Bubbletea
spinner.Init()
view := spinner.View()

Common use cases:

  • Loading indicators
  • Background processing
  • Data fetching states
  • Long-running operations

Note: This is a simplified spinner implementation. For advanced animations with Bubbletea tick messages, consider using the bubbles/spinner package directly.

Accessibility:

  • Clear visual indication of activity
  • Optional label for context
  • Can be hidden when inactive

func Table

func Table[T any](props TableProps[T]) bubbly.Component

func Tabs

func Tabs(props TabsProps) bubbly.Component

Tabs creates a tabbed interface component. The tabs component displays multiple content panels with tab buttons for switching.

Features:

  • Multiple tabs with labels
  • Active tab highlighting
  • Reactive active index
  • OnTabChange callback
  • String or Component content
  • Theme integration
  • Custom style override

Example:

activeIndex := bubbly.NewRef(0)
tabs := Tabs(TabsProps{
    Tabs: []Tab{
        {Label: "Profile", Content: "User profile content"},
        {Label: "Settings", Content: "Settings content"},
        {Label: "Security", Content: "Security content"},
    },
    ActiveIndex: activeIndex,
    OnTabChange: func(index int) {
        loadTabContent(index)
    },
})

func Text

func Text(props TextProps) bubbly.Component

Text creates a new Text atom component.

Text is a fundamental display element for rendering styled text in the terminal. It supports various formatting options including bold, italic, underline, strikethrough, colors, and alignment.

The text component automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

text := components.Text(components.TextProps{
    Content:   "Welcome to BubblyUI!",
    Bold:      true,
    Color:     lipgloss.Color("99"),
    Alignment: components.AlignCenter,
    Width:     40,
})

// Initialize and use with Bubbletea
text.Init()
view := text.View()

Formatting options:

  • Bold: Makes text bold
  • Italic: Makes text italic
  • Underline: Underlines text
  • Strikethrough: Strikes through text
  • Color: Sets text color
  • Background: Sets background color
  • Alignment: Aligns text (requires Width)
  • Width/Height: Constrains text dimensions

Accessibility:

  • Clear visual distinction with formatting options
  • High contrast colors available via theme
  • Supports all terminal color profiles

func TextArea

func TextArea(props TextAreaProps) bubbly.Component

func Toggle

func Toggle(props ToggleProps) bubbly.Component

Toggle creates a new Toggle molecule component.

Toggle is an interactive switch element that allows users to turn a feature on or off. It supports reactive state binding, callbacks, and disabled states.

The toggle automatically integrates with the theme system via the composition API's Provide/Inject mechanism. If no theme is provided, it uses DefaultTheme.

Example:

valueRef := bubbly.NewRef(false)
toggle := components.Toggle(components.ToggleProps{
    Label: "Enable notifications",
    Value: valueRef,
    OnChange: func(enabled bool) {
        if enabled {
            enableNotifications()
        } else {
            disableNotifications()
        }
    },
})

// Initialize and use with Bubbletea
toggle.Init()
view := toggle.View()

Features:

  • Reactive on/off state binding with Ref[bool]
  • Toggle functionality via "toggle" event
  • OnChange callback support
  • Disabled state support
  • Label display
  • Theme integration
  • Custom style override

Keyboard interaction:

  • Space/Enter: Toggle switch (when focused)

Visual indicators:

  • Off: [OFF] or [─●]
  • On: [ON ] or [●─]

Accessibility:

  • Clear visual distinction between on/off states
  • Disabled state clearly indicated
  • Keyboard accessible

func VStack

func VStack(props StackProps) bubbly.Component

VStack creates a vertical stack layout component. Items are arranged vertically (top to bottom) with configurable spacing and cross-axis (horizontal) alignment.

Features:

  • Vertical arrangement of child components
  • Configurable spacing between items (in lines)
  • Cross-axis alignment (start/center/end/stretch)
  • Optional dividers between items
  • Theme integration for divider styling
  • Custom style override support

Example:

// Simple vertical stack
vstack := VStack(StackProps{
    Items: []interface{}{header, content, footer},
    Spacing: 1,
})

// With alignment and dividers
vstack := VStack(StackProps{
    Items:   []interface{}{title, description, actions},
    Align:   AlignItemsCenter,
    Divider: true,
})

// Form layout pattern
vstack := VStack(StackProps{
    Items: []interface{}{
        HStack(StackProps{Items: []interface{}{label1, input1}}),
        HStack(StackProps{Items: []interface{}{label2, input2}}),
        HStack(StackProps{Items: []interface{}{cancelBtn, submitBtn}}),
    },
    Spacing: 1,
})

Types

type AccordionItem

type AccordionItem struct {
	// Title is the panel header text.
	Title string

	// Content is the panel body text.
	Content string

	// Component is an optional component to render as content.
	// If provided, takes precedence over Content string.
	Component bubbly.Component
}

AccordionItem represents a single accordion panel.

type AccordionProps

type AccordionProps struct {
	// Items is the list of accordion panels.
	// Required - should not be empty.
	Items []AccordionItem

	// ExpandedIndexes is the reactive reference to the list of expanded panel indexes.
	// Optional - if nil, no panels are expanded initially.
	ExpandedIndexes *bubbly.Ref[[]int]

	// AllowMultiple allows multiple panels to be expanded simultaneously.
	// If false, expanding a panel collapses others.
	// Default is false.
	AllowMultiple bool

	// OnToggle is called when a panel is toggled.
	// Receives the panel index and new expanded state.
	// Optional - if nil, no callback is executed.
	OnToggle func(int, bool)

	// Width sets the accordion width in characters.
	// Default is 50 if not specified.
	Width int

	// CommonProps for styling and identification.
	CommonProps
}

AccordionProps defines the properties for the Accordion component. Accordion is an organism component that displays collapsible panels.

type AlignItems

type AlignItems string

AlignItems specifies how items are aligned along the cross axis. This follows CSS flexbox align-items semantics.

const (
	// AlignItemsStart aligns items to the start of the cross axis.
	AlignItemsStart AlignItems = "start"

	// AlignItemsCenter centers items along the cross axis.
	AlignItemsCenter AlignItems = "center"

	// AlignItemsEnd aligns items to the end of the cross axis.
	AlignItemsEnd AlignItems = "end"

	// AlignItemsStretch stretches items to fill the cross axis.
	AlignItemsStretch AlignItems = "stretch"
)

func (AlignItems) IsValid

func (a AlignItems) IsValid() bool

IsValid returns true if the AlignItems is a valid constant.

type Alignment

type Alignment string

Alignment represents text or content alignment.

const (
	// AlignLeft aligns content to the left.
	AlignLeft Alignment = "left"

	// AlignCenter centers content.
	AlignCenter Alignment = "center"

	// AlignRight aligns content to the right.
	AlignRight Alignment = "right"
)

Common alignment constants.

type AppLayoutProps

type AppLayoutProps struct {
	// Header is the top section component (optional).
	// Typically contains application title, navigation, or branding.
	Header bubbly.Component

	// Sidebar is the left section component (optional).
	// Typically contains navigation menu or secondary content.
	Sidebar bubbly.Component

	// Content is the main center section component (required).
	// Contains the primary application content.
	Content bubbly.Component

	// Footer is the bottom section component (optional).
	// Typically contains copyright, links, or status information.
	Footer bubbly.Component

	// Width sets the total layout width in characters.
	// Default is 80 if not specified.
	Width int

	// Height sets the total layout height in lines.
	// Default is 24 if not specified.
	Height int

	// SidebarWidth sets the sidebar width in characters.
	// Default is 20 if not specified.
	SidebarWidth int

	// HeaderHeight sets the header height in lines.
	// Default is 3 if not specified.
	HeaderHeight int

	// FooterHeight sets the footer height in lines.
	// Default is 2 if not specified.
	FooterHeight int

	// CommonProps for styling and identification.
	CommonProps
}

AppLayoutProps defines the properties for the AppLayout component. AppLayout is a template component that provides a full application layout structure.

type BadgeProps

type BadgeProps struct {
	// Label is the text content to display in the badge.
	// Required - the main text to show.
	Label string

	// Variant determines the visual style of the badge.
	// Valid values: VariantPrimary, VariantSecondary, VariantSuccess, VariantWarning, VariantDanger, VariantInfo.
	// Optional - defaults to VariantPrimary if not specified.
	Variant Variant

	// Color sets a custom foreground color for the badge.
	// Overrides the variant color if specified.
	// Optional - if not specified, uses variant color from theme.
	Color lipgloss.Color

	// Common props for all components
	CommonProps
}

BadgeProps defines the configuration properties for a Badge component.

Example usage:

badge := components.Badge(components.BadgeProps{
    Label:   "New",
    Variant: components.VariantSuccess,
})

type BoxProps

type BoxProps struct {
	// Child is an optional child component to render inside the box.
	// If provided, it takes precedence over Content.
	Child bubbly.Component

	// Content is the text content to display inside the box.
	// Used when Child is nil.
	Content string

	// Padding sets uniform padding on all sides (in characters/lines).
	// Default is 0 (no padding).
	Padding int

	// PaddingX sets horizontal padding (left and right).
	// If set, overrides Padding for horizontal sides.
	PaddingX int

	// PaddingY sets vertical padding (top and bottom).
	// If set, overrides Padding for vertical sides.
	PaddingY int

	// Border enables a border around the box.
	// Default is false (no border).
	Border bool

	// BorderStyle specifies the border style to use.
	// Default is lipgloss.NormalBorder() when Border is true.
	BorderStyle lipgloss.Border

	// Title is optional text displayed at the top of the box.
	// Rendered as a styled header line inside the box.
	Title string

	// Width sets the fixed width of the box in characters.
	// Default is 0 (auto-width based on content).
	Width int

	// Height sets the fixed height of the box in lines.
	// Default is 0 (auto-height based on content).
	Height int

	// Background sets the background color inside the box.
	// Default is no background color.
	Background lipgloss.Color

	// CommonProps for styling and identification.
	CommonProps
}

BoxProps defines the properties for the Box container component. Box is an atom component that provides a generic container with padding, border, and title support. It serves as a building block for higher-level layouts.

type ButtonProps

type ButtonProps struct {
	// Label is the text displayed on the button.
	// Required - should not be empty for usability.
	Label string

	// Variant determines the visual style of the button.
	// Defaults to ButtonPrimary if not specified.
	// Valid values: ButtonPrimary, ButtonSecondary, ButtonDanger, ButtonSuccess, ButtonWarning, ButtonInfo.
	Variant ButtonVariant

	// Disabled indicates whether the button is disabled.
	// Disabled buttons do not respond to click events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// OnClick is the callback function executed when the button is clicked.
	// Only called if the button is not disabled.
	// Optional - if nil, button will not respond to clicks.
	OnClick func()

	// NoBorder removes the border if true.
	// Default is false (border is shown).
	// Useful when embedding in other bordered containers.
	NoBorder bool

	// Common props for all components
	CommonProps
}

ButtonProps defines the configuration properties for a Button component.

Example usage:

button := components.Button(components.ButtonProps{
    Label:   "Submit",
    Variant: components.ButtonPrimary,
    OnClick: func() {
        handleSubmit()
    },
})

type ButtonVariant

type ButtonVariant string

ButtonVariant defines the visual style variant of a button. Variants map to theme colors for consistent styling across the application.

const (
	// ButtonPrimary represents the primary/default button variant.
	// Used for main actions and primary calls-to-action.
	ButtonPrimary ButtonVariant = "primary"

	// ButtonSecondary represents a secondary/alternative button variant.
	// Used for less prominent actions.
	ButtonSecondary ButtonVariant = "secondary"

	// ButtonDanger represents a destructive/dangerous action variant.
	// Used for delete, remove, or other destructive operations.
	ButtonDanger ButtonVariant = "danger"

	// ButtonSuccess represents a successful/positive action variant.
	// Used for confirmations and positive actions.
	ButtonSuccess ButtonVariant = "success"

	// ButtonWarning represents a warning/caution variant.
	// Used for actions that require user attention.
	ButtonWarning ButtonVariant = "warning"

	// ButtonInfo represents an informational variant.
	// Used for neutral informational actions.
	ButtonInfo ButtonVariant = "info"
)

Button variant constants.

type CardProps

type CardProps struct {
	// Title is the card header text.
	// Optional - if empty, no title is displayed.
	Title string

	// Content is the main body text of the card.
	// Optional - can be empty if using Children instead.
	Content string

	// Footer is optional footer text displayed at the bottom.
	// Optional - if empty, no footer is displayed.
	Footer string

	// Children are optional child components rendered in the card body.
	// These are rendered after Content if both are provided.
	// Optional - if empty, only Content is displayed.
	Children []bubbly.Component

	// Width sets the card width in characters.
	// Default is 40 if not specified.
	Width int

	// Height sets the card height in lines.
	// Default is auto-height based on content if not specified.
	Height int

	// Padding sets the internal padding.
	// Default is 1 if not specified.
	Padding int

	// NoBorder removes the border if true.
	// Default is false (border is shown).
	NoBorder bool

	// CommonProps for styling and identification.
	CommonProps
}

CardProps defines the properties for the Card component. Card is an organism component that displays a content container with optional title and footer.

type CenterProps

type CenterProps struct {
	// Child is the component to center within the container.
	// If nil, the Center component renders an empty container.
	Child bubbly.Component

	// Width sets the container width in characters.
	// Default is 0 (auto-width based on content).
	// Horizontal centering requires a Width > 0.
	Width int

	// Height sets the container height in lines.
	// Default is 0 (auto-height based on content).
	// Vertical centering requires a Height > 0.
	Height int

	// Horizontal enables horizontal-only centering when true.
	// When false and Vertical is also false, centers both directions (default).
	// Default is false.
	Horizontal bool

	// Vertical enables vertical-only centering when true.
	// When false and Horizontal is also false, centers both directions (default).
	// Default is false.
	Vertical bool

	// CommonProps for styling and identification.
	CommonProps
}

CenterProps defines the properties for the Center layout component. Center is a molecule component that centers its child component horizontally and/or vertically within a container of specified dimensions.

By default, when neither Horizontal nor Vertical flags are set, the component centers content in both directions. Setting either flag to true enables centering only in that specific direction.

type CheckboxProps

type CheckboxProps struct {
	// Label is the text displayed next to the checkbox.
	// Optional - if empty, only the checkbox indicator is shown.
	Label string

	// Checked is the reactive reference to the checkbox's checked state.
	// Required - must be a valid Ref[bool].
	// Changes to this ref will update the checkbox display.
	Checked *bubbly.Ref[bool]

	// OnChange is a callback function executed when the checkbox is toggled.
	// Receives the new checked state as a parameter.
	// Optional - if nil, no callback is executed.
	OnChange func(bool)

	// Disabled indicates whether the checkbox is disabled.
	// Disabled checkboxes do not respond to toggle events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// Common props for all components
	CommonProps
}

CheckboxProps defines the configuration properties for a Checkbox component.

Example usage:

checkedRef := bubbly.NewRef(false)
checkbox := components.Checkbox(components.CheckboxProps{
    Label:   "Accept terms and conditions",
    Checked: checkedRef,
    OnChange: func(checked bool) {
        fmt.Println("Checkbox is now:", checked)
    },
})

type ClassName

type ClassName string

ClassName represents a CSS-like class name for styling.

type CommonProps

type CommonProps struct {
	// ID is a unique identifier for the component instance.
	// Optional - if not provided, components generate their own IDs.
	ID ComponentID

	// ClassName allows applying custom styling classes.
	// Optional - used for custom theming and styling overrides.
	ClassName ClassName

	// Style provides direct Lipgloss style overrides.
	// Optional - takes precedence over theme and className styles.
	Style *lipgloss.Style
}

CommonProps contains properties shared by all components. These props provide consistent styling and identification across the component library.

type ComponentID

type ComponentID string

ComponentID is a unique identifier for a component instance.

type ContainerProps

type ContainerProps struct {
	// Child is the component to render inside the container.
	// If nil, the Container renders an empty space with the specified width.
	Child bubbly.Component

	// Size is a preset container size that determines the max-width.
	// Available sizes: ContainerSm (40), ContainerMd (60), ContainerLg (80),
	// ContainerXl (100), ContainerFull (no constraint).
	// Default is ContainerMd (60 characters).
	Size ContainerSize

	// MaxWidth overrides Size with a custom maximum width in characters.
	// When MaxWidth > 0, it takes precedence over the Size preset.
	// Default is 0 (use Size preset).
	MaxWidth int

	// Centered horizontally centers the content within the container width.
	// When true, content is centered using Lipgloss alignment.
	// Default is true.
	// Use CenteredSet to explicitly disable centering (set Centered=false, CenteredSet=true).
	Centered bool

	// CenteredSet indicates whether Centered was explicitly set.
	// This allows distinguishing between "not set" (default true) and "explicitly false".
	CenteredSet bool

	// CommonProps for styling and identification.
	CommonProps
}

ContainerProps defines the properties for the Container layout component. Container is a molecule component that constrains content width and optionally centers it horizontally within the available space.

Container is useful for creating readable content layouts by limiting line length to comfortable reading widths (typically 60-80 characters).

type ContainerSize

type ContainerSize string

ContainerSize specifies preset container widths for readable content. These sizes are optimized for terminal layouts.

const (
	// ContainerSm is a small container (40 characters wide).
	ContainerSm ContainerSize = "sm"

	// ContainerMd is a medium container (60 characters wide).
	ContainerMd ContainerSize = "md"

	// ContainerLg is a large container (80 characters wide).
	ContainerLg ContainerSize = "lg"

	// ContainerXl is an extra-large container (100 characters wide).
	ContainerXl ContainerSize = "xl"

	// ContainerFull uses full available width (100%).
	ContainerFull ContainerSize = "full"
)

func (ContainerSize) IsValid

func (s ContainerSize) IsValid() bool

IsValid returns true if the ContainerSize is a valid constant.

func (ContainerSize) Width

func (s ContainerSize) Width() int

Width returns the preset width in characters for the ContainerSize. Returns 0 for ContainerFull (meaning 100%/auto) or unknown sizes.

type DividerProps

type DividerProps struct {
	// Vertical renders a vertical divider if true.
	// Default: false (horizontal)
	Vertical bool

	// Length is the divider length in characters (horizontal) or lines (vertical).
	// Default: 20
	Length int

	// Label is optional text centered on the divider.
	// For horizontal dividers, the label appears in the middle of the line.
	// For vertical dividers, the label appears in the middle row.
	Label string

	// Char is the divider character.
	// Default: "─" (horizontal) or "│" (vertical)
	Char string

	// CommonProps for styling and identification.
	CommonProps
}

DividerProps defines the properties for the Divider component. Divider is an atom component that renders a horizontal or vertical separator line with optional label text.

type EventHandler

type EventHandler func(data interface{})

EventHandler is a generic event handler function. It receives event data and performs an action.

type FlexDirection

type FlexDirection string

FlexDirection specifies the main axis direction for Flex layouts. Use FlexRow for horizontal layouts and FlexColumn for vertical layouts.

const (
	// FlexRow arranges items horizontally (left to right).
	FlexRow FlexDirection = "row"

	// FlexColumn arranges items vertically (top to bottom).
	FlexColumn FlexDirection = "column"
)

func (FlexDirection) IsValid

func (d FlexDirection) IsValid() bool

IsValid returns true if the FlexDirection is a valid constant.

type FlexProps

type FlexProps struct {
	// Items are the child components to arrange.
	Items []bubbly.Component

	// Direction specifies row (horizontal) or column (vertical).
	// Default: FlexRow
	Direction FlexDirection

	// Justify controls main-axis distribution.
	// Default: JustifyStart
	Justify JustifyContent

	// Align controls cross-axis alignment.
	// Default: AlignItemsStart
	Align AlignItems

	// Gap is the spacing between items in characters.
	// Default: 0
	Gap int

	// Wrap enables wrapping items to next row/column when they exceed
	// the container size. Requires Width (for row) or Height (for column).
	// Default: false
	Wrap bool

	// Width sets fixed container width. 0 = auto.
	Width int

	// Height sets fixed container height. 0 = auto.
	Height int

	// CommonProps for styling and identification.
	CommonProps
}

FlexProps defines properties for the Flex layout component. Flex provides flexbox-style layout with direction, justify, and align options.

Example:

// Row layout with space-between
flex := Flex(FlexProps{
    Items:     []bubbly.Component{btn1, btn2, btn3},
    Direction: FlexRow,
    Justify:   JustifySpaceBetween,
    Gap:       2,
})

// Column layout centered
flex := Flex(FlexProps{
    Items:     []bubbly.Component{header, content, footer},
    Direction: FlexColumn,
    Justify:   JustifyCenter,
    Align:     AlignItemsCenter,
})

type FormField

type FormField struct {
	// Name is the unique identifier for this field.
	// Used for validation error mapping.
	// Required - must be unique within the form.
	Name string

	// Label is the display text shown above the field.
	// Optional - if empty, no label is displayed.
	Label string

	// Component is the input component for this field.
	// Can be Input, Checkbox, Select, TextArea, Radio, Toggle, etc.
	// Required - must be a valid component.
	Component bubbly.Component
}

FormField represents a single field in a form. Each field has a name (for identification), a label (for display), and a component (the actual input/select/checkbox/etc.).

type FormProps

type FormProps[T any] struct {
	// Initial is the initial form data.
	// Used to populate the form on first render and for reset functionality.
	// Required - must be a valid struct of type T.
	Initial T

	// Validate is a function that validates the entire form.
	// Returns a map of field names to error messages.
	// Optional - if nil, no validation is performed.
	// Called on submit and optionally on field changes.
	Validate func(T) map[string]string

	// OnSubmit is a callback function executed when the form is submitted.
	// Only called if validation passes (no errors).
	// Receives the validated form data as a parameter.
	// Optional - if nil, no callback is executed.
	OnSubmit func(T)

	// OnCancel is a callback function executed when the form is canceled.
	// Optional - if nil, no callback is executed.
	OnCancel func()

	// Fields is the list of form fields to display.
	// Each field includes a name, label, and component.
	// Required - should not be empty for usability.
	Fields []FormField

	// Common props for all components
	CommonProps
}

FormProps defines the configuration properties for a Form component.

Form is a generic component that works with any struct type T. It manages form state, validation, and submission.

Example usage:

type UserData struct {
    Name  string
    Email string
    Age   int
}

nameRef := bubbly.NewRef("")
emailRef := bubbly.NewRef("")

form := components.Form(components.FormProps[UserData]{
    Initial: UserData{},
    Fields: []components.FormField{
        {
            Name:  "Name",
            Label: "Full Name",
            Component: components.Input(components.InputProps{
                Value: nameRef,
                Placeholder: "Enter your name",
            }),
        },
        {
            Name:  "Email",
            Label: "Email Address",
            Component: components.Input(components.InputProps{
                Value: emailRef,
                Type: components.InputEmail,
            }),
        },
    },
    Validate: func(data UserData) map[string]string {
        errors := make(map[string]string)
        if data.Name == "" {
            errors["Name"] = "Name is required"
        }
        if data.Email == "" {
            errors["Email"] = "Email is required"
        }
        return errors
    },
    OnSubmit: func(data UserData) {
        saveUser(data)
    },
})

type GridLayoutProps

type GridLayoutProps struct {
	// Items are the components to arrange in the grid.
	// Required - the components to display in grid cells.
	Items []bubbly.Component

	// Columns sets the number of columns in the grid.
	// Default is 1 if not specified.
	Columns int

	// Gap sets the spacing between grid cells in characters.
	// Default is 1 if not specified.
	Gap int

	// CellWidth sets the width of each grid cell in characters.
	// Default is 20 if not specified.
	CellWidth int

	// CellHeight sets the height of each grid cell in lines.
	// Default is 0 (auto-height based on content) if not specified.
	CellHeight int

	// CommonProps for styling and identification.
	CommonProps
}

GridLayoutProps defines the properties for the GridLayout component. GridLayout is a template component that provides a grid-based layout system.

type IconProps

type IconProps struct {
	// Symbol is the icon character or glyph to display.
	// Can be any Unicode character, emoji, or symbol.
	// Required - the main icon to render.
	Symbol string

	// Color sets the foreground color of the icon.
	// Supports ANSI colors, 256-color palette, and true color.
	// Optional - if not specified, uses theme foreground color.
	Color lipgloss.Color

	// Size determines the visual size of the icon.
	// Valid values: SizeSmall, SizeMedium, SizeLarge.
	// Optional - if not specified, uses natural size.
	Size Size

	// Common props for all components
	CommonProps
}

IconProps defines the configuration properties for an Icon component.

Example usage:

icon := components.Icon(components.IconProps{
    Symbol: "✓",
    Color:  lipgloss.Color("46"),
    Size:   components.SizeMedium,
})

type InputProps

type InputProps struct {
	// Value is the reactive reference to the input's value.
	// Required - must be a valid Ref[string].
	// Changes to this ref will update the input display.
	Value *bubbly.Ref[string]

	// Placeholder is the text displayed when the input is empty.
	// Optional - if empty, no placeholder is shown.
	Placeholder string

	// Type determines the input field type.
	// Valid values: InputText, InputPassword, InputEmail.
	// Default: InputText.
	Type InputType

	// Validate is a function called when the value changes.
	// If it returns an error, the error message is displayed below the input.
	// Optional - if nil, no validation is performed.
	Validate func(string) error

	// OnChange is a callback function executed when the value changes.
	// Called after validation.
	// Optional - if nil, no callback is executed.
	OnChange func(string)

	// OnBlur is a callback function executed when the input loses focus.
	// Optional - if nil, no callback is executed.
	OnBlur func()

	// Width sets the width of the input field in characters.
	// Optional - if 0, defaults to 30 characters.
	Width int

	// CharLimit sets the maximum number of characters allowed.
	// Optional - if 0, no limit is enforced.
	CharLimit int

	// ShowCursorPosition displays the cursor position indicator [pos/len].
	// Optional - defaults to false.
	ShowCursorPosition bool

	// NoBorder removes the border if true.
	// Default is false (border is shown).
	// Useful when embedding in other bordered containers.
	NoBorder bool

	// Common props for all components
	CommonProps
}

InputProps defines the configuration properties for an Input component.

Example usage:

valueRef := bubbly.NewRef("")
input := components.Input(components.InputProps{
    Value:       valueRef,
    Placeholder: "Enter your name",
    Type:        components.InputText,
    Validate: func(s string) error {
        if len(s) < 3 {
            return errors.New("name must be at least 3 characters")
        }
        return nil
    },
})

type InputType

type InputType string

InputType defines the type of input field. Different types may have different validation and display behavior.

const (
	// InputText represents a standard text input field.
	// Displays characters as typed.
	InputText InputType = "text"

	// InputPassword represents a password input field.
	// Masks characters with asterisks for security.
	InputPassword InputType = "password"

	// InputEmail represents an email input field.
	// Displays characters as typed, intended for email addresses.
	InputEmail InputType = "email"
)

Input type constants.

type JustifyContent

type JustifyContent string

JustifyContent specifies how items are distributed along the main axis. This follows CSS flexbox justify-content semantics.

const (
	// JustifyStart aligns items to the start of the container.
	JustifyStart JustifyContent = "start"

	// JustifyCenter centers items in the container.
	JustifyCenter JustifyContent = "center"

	// JustifyEnd aligns items to the end of the container.
	JustifyEnd JustifyContent = "end"

	// JustifySpaceBetween distributes items with equal space between them.
	// First item at start, last item at end.
	JustifySpaceBetween JustifyContent = "space-between"

	// JustifySpaceAround distributes items with equal space around them.
	// Half-size space on the edges.
	JustifySpaceAround JustifyContent = "space-around"

	// JustifySpaceEvenly distributes items with equal space everywhere.
	// Equal space between items and on edges.
	JustifySpaceEvenly JustifyContent = "space-evenly"
)

func (JustifyContent) IsValid

func (j JustifyContent) IsValid() bool

IsValid returns true if the JustifyContent is a valid constant.

type ListProps

type ListProps[T any] struct {
	// Items is a reactive reference to the list data.
	// Must be a slice of type T.
	// Required - updates trigger re-renders.
	Items *bubbly.Ref[[]T]

	// RenderItem is a function that renders each item.
	// Receives the item and its index as parameters.
	// Required - defines how each item is displayed.
	RenderItem func(T, int) string

	// Height is the visible height of the list in lines.
	// Items beyond this height require scrolling.
	// Optional - defaults to 10 if not specified.
	Height int

	// Virtual enables virtual scrolling for large lists.
	// When true, only visible items are rendered for performance.
	// Recommended for lists with 100+ items.
	// Optional - defaults to false.
	Virtual bool

	// OnSelect is a callback function executed when an item is selected.
	// Receives the selected item and its index as parameters.
	// Optional - if nil, no callback is executed.
	OnSelect func(T, int)

	// Common props for all components
	CommonProps
}

ListProps defines the configuration properties for a List component.

List is a generic component that works with any slice type []T. It displays a scrollable list of items with optional virtual scrolling for performance, keyboard navigation, and item selection.

Example usage:

type Todo struct {
    Title     string
    Completed bool
}

todosData := bubbly.NewRef([]Todo{
    {Title: "Buy groceries", Completed: false},
    {Title: "Write code", Completed: true},
})

list := components.List(components.ListProps[Todo]{
    Items: todosData,
    RenderItem: func(todo Todo, index int) string {
        checkbox := "☐"
        if todo.Completed {
            checkbox = "☑"
        }
        return fmt.Sprintf("%s %s", checkbox, todo.Title)
    },
    Height: 10,
    Virtual: true,
})
type MenuItem struct {
	// Label is the display text for the menu item.
	Label string

	// Value is the unique identifier for the menu item.
	Value string

	// Disabled indicates if the menu item is disabled.
	Disabled bool
}

MenuItem represents a single menu item.

type MenuProps struct {
	// Items is the list of menu items to display.
	// Required - should not be empty.
	Items []MenuItem

	// Selected is the reactive reference to the currently selected item value.
	// Optional - if nil, no item is selected initially.
	Selected *bubbly.Ref[string]

	// OnSelect is called when a menu item is selected.
	// Receives the selected item's value.
	// Optional - if nil, no callback is executed.
	OnSelect func(string)

	// Width sets the menu width in characters.
	// Default is 30 if not specified.
	Width int

	// CommonProps for styling and identification.
	CommonProps
}

MenuProps defines the properties for the Menu component. Menu is an organism component that displays a navigable list of items.

type ModalProps

type ModalProps struct {
	// Title is the modal header text.
	Title string

	// Content is the main body text of the modal.
	Content string

	// Visible controls whether the modal is displayed.
	// When false, the modal renders nothing.
	Visible *bubbly.Ref[bool]

	// Width sets the modal width in characters.
	// Default is 50 if not specified.
	Width int

	// Buttons are optional action buttons displayed at the bottom.
	Buttons []bubbly.Component

	// OnClose is called when the modal is closed (Esc key).
	OnClose func()

	// OnConfirm is called when Enter key is pressed.
	OnConfirm func()

	// CommonProps for styling and identification.
	CommonProps
}

ModalProps defines the properties for the Modal component. Modal is an organism component that displays an overlay dialog.

type PageLayoutProps

type PageLayoutProps struct {
	// Title is the page title section component (optional).
	// Typically contains page heading or breadcrumbs.
	Title bubbly.Component

	// Content is the main page content section component (required).
	// Contains the primary page content.
	Content bubbly.Component

	// Actions is the page actions section component (optional).
	// Typically contains buttons or action controls at the bottom.
	Actions bubbly.Component

	// Width sets the total layout width in characters.
	// Default is 80 if not specified.
	Width int

	// Spacing sets the vertical spacing between sections in lines.
	// Default is 2 if not specified.
	Spacing int

	// CommonProps for styling and identification.
	CommonProps
}

PageLayoutProps defines the properties for the PageLayout component. PageLayout is a template component that provides a simple page structure.

type PanelLayoutProps

type PanelLayoutProps struct {
	// Left is the left panel component (or top panel in vertical mode).
	// Optional - if empty, only Right panel is displayed.
	Left bubbly.Component

	// Right is the right panel component (or bottom panel in vertical mode).
	// Optional - if empty, only Left panel is displayed.
	Right bubbly.Component

	// Direction sets the split direction: "horizontal" (left/right) or "vertical" (top/bottom).
	// Default is "horizontal" if not specified.
	Direction string

	// SplitRatio sets the ratio between left/right (or top/bottom) panels.
	// Value between 0.0 and 1.0. Default is 0.5 (50/50 split).
	// For example, 0.3 means 30% left, 70% right.
	SplitRatio float64

	// Width sets the total layout width in characters.
	// Default is 80 if not specified.
	Width int

	// Height sets the total layout height in lines.
	// Default is 24 if not specified.
	Height int

	// ShowBorder enables borders around panels.
	// Default is false.
	ShowBorder bool

	// CommonProps for styling and identification.
	CommonProps
}

PanelLayoutProps defines the properties for the PanelLayout component. PanelLayout is a template component that provides a split panel layout.

type Position

type Position string

Position represents the position of an element.

const (
	// PositionTop positions element at the top.
	PositionTop Position = "top"

	// PositionBottom positions element at the bottom.
	PositionBottom Position = "bottom"

	// PositionLeft positions element on the left.
	PositionLeft Position = "left"

	// PositionRight positions element on the right.
	PositionRight Position = "right"
)

Common position constants.

type RadioProps

type RadioProps[T any] struct {
	// Value is the reactive reference to the selected value.
	// Required - must be a valid Ref[T].
	// Changes to this ref will update the radio display.
	Value *bubbly.Ref[T]

	// Options is the list of available options to choose from.
	// Required - should not be empty for usability.
	Options []T

	// OnChange is a callback function executed when the selection changes.
	// Receives the newly selected value as a parameter.
	// Optional - if nil, no callback is executed.
	OnChange func(T)

	// Disabled indicates whether the radio is disabled.
	// Disabled radios do not respond to events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// RenderOption is a custom function to render each option as a string.
	// Optional - if nil, uses fmt.Sprintf("%v", option) for default rendering.
	// Useful for complex types that need custom display logic.
	RenderOption func(T) string

	// Common props for all components
	CommonProps
}

RadioProps defines the configuration properties for a Radio component.

Radio is a generic component that works with any type T.

Example usage:

valueRef := bubbly.NewRef("option1")
radio := components.Radio(components.RadioProps[string]{
    Value:   valueRef,
    Options: []string{"option1", "option2", "option3"},
    OnChange: func(value string) {
        fmt.Println("Selected:", value)
    },
})

type RenderFunc

type RenderFunc func() string

RenderFunc is a function that renders content to a string. Used for custom rendering in components like List and Table.

type SelectProps

type SelectProps[T any] struct {
	// Value is the reactive reference to the selected value.
	// Required - must be a valid Ref[T].
	// Changes to this ref will update the select display.
	Value *bubbly.Ref[T]

	// Options is the list of available options to choose from.
	// Required - should not be empty for usability.
	Options []T

	// OnChange is a callback function executed when the selection changes.
	// Receives the newly selected value as a parameter.
	// Optional - if nil, no callback is executed.
	OnChange func(T)

	// Placeholder is the text displayed when no value is selected.
	// Optional - if empty, shows the first option or empty string.
	Placeholder string

	// Disabled indicates whether the select is disabled.
	// Disabled selects do not respond to events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// RenderOption is a custom function to render each option as a string.
	// Optional - if nil, uses fmt.Sprintf("%v", option) for default rendering.
	// Useful for complex types that need custom display logic.
	RenderOption func(T) string

	// Width sets the width of the select in characters.
	// Optional - if 0, defaults to 30 characters.
	Width int

	// NoBorder removes the border if true.
	// Default is false (border is shown).
	// Useful when embedding in other bordered containers.
	NoBorder bool

	// Common props for all components
	CommonProps
}

SelectProps defines the configuration properties for a Select component.

Select is a generic component that works with any type T.

Example usage:

valueRef := bubbly.NewRef("option1")
selectComp := components.Select(components.SelectProps[string]{
    Value:   valueRef,
    Options: []string{"option1", "option2", "option3"},
    OnChange: func(value string) {
        fmt.Println("Selected:", value)
    },
})

type Size

type Size string

Size represents the size of a component. Common sizes include "small", "medium", "large".

const (
	// SizeSmall represents a small component size.
	SizeSmall Size = "small"

	// SizeMedium represents a medium/default component size.
	SizeMedium Size = "medium"

	// SizeLarge represents a large component size.
	SizeLarge Size = "large"
)

Common size constants used across multiple components.

type SpacerProps

type SpacerProps struct {
	// Flex makes the spacer fill available space in parent layouts.
	// When true, the spacer expands to fill remaining space in HStack/VStack/Flex.
	// Width and Height become minimum dimensions when Flex is true.
	// Default: false (fixed-size spacer)
	Flex bool

	// Width sets the horizontal space in characters.
	// When Flex=false: exact width to render.
	// When Flex=true: minimum width (parent layout may expand beyond this).
	// Optional - if 0, no horizontal space is added (or auto when Flex=true).
	Width int

	// Height sets the vertical space in lines.
	// When Flex=false: exact height to render.
	// When Flex=true: minimum height (parent layout may expand beyond this).
	// Optional - if 0, no vertical space is added (or auto when Flex=true).
	Height int

	// Common props for all components
	CommonProps
}

SpacerProps defines the configuration properties for a Spacer component.

Example usage:

// Fixed-size spacer
spacer := components.Spacer(components.SpacerProps{
    Width:  20,
    Height: 3,
})

// Flexible spacer (fills available space in parent layout)
flexSpacer := components.Spacer(components.SpacerProps{
    Flex: true,
})

func (SpacerProps) IsFlex

func (p SpacerProps) IsFlex() bool

IsFlex returns true if this spacer should fill available space. Parent layouts (HStack, VStack, Flex) use this to detect flexible spacers and distribute remaining space accordingly.

type SpinnerProps

type SpinnerProps struct {
	// Label is optional text to display next to the spinner.
	// Typically used to describe what is loading.
	// Optional - if empty, only the spinner animation is shown.
	Label string

	// Active determines whether the spinner is animating.
	// When false, the spinner is hidden or shows a static state.
	// Default: false.
	Active bool

	// Color sets the foreground color of the spinner.
	// Optional - if not specified, uses theme primary color.
	Color lipgloss.Color

	// Common props for all components
	CommonProps
}

SpinnerProps defines the configuration properties for a Spinner component.

Example usage:

spinner := components.Spinner(components.SpinnerProps{
    Label:  "Loading...",
    Active: true,
    Color:  lipgloss.Color("99"),
})

type StackProps

type StackProps struct {
	// Items are the child components to stack.
	// Each item can be any bubbly.Component.
	Items []interface{}

	// Spacing between items in characters (HStack) or lines (VStack).
	// Default: 1
	Spacing int

	// Align controls cross-axis alignment.
	// For HStack: vertical alignment (top/center/bottom)
	// For VStack: horizontal alignment (left/center/right)
	// Default: AlignItemsStart
	Align AlignItems

	// Divider optionally renders a divider between items.
	// Default: false
	Divider bool

	// DividerChar is the character for dividers.
	// Default: "│" for HStack, "─" for VStack
	DividerChar string

	// CommonProps for styling and identification.
	CommonProps
}

StackProps defines the properties for HStack and VStack components. Stack components provide simplified stacking layouts with spacing and alignment.

type Tab

type Tab struct {
	// Label is the tab button text.
	Label string

	// Content is the content to display when tab is active.
	// Can be a string or use a Component for dynamic content.
	Content string

	// Component is an optional component to render as content.
	// If provided, takes precedence over Content string.
	Component bubbly.Component
}

Tab represents a single tab with label and content.

type TableColumn

type TableColumn[T any] struct {
	// Header is the display text shown in the table header row.
	// Required - should be descriptive of the column content.
	Header string

	// Field is the name of the struct field to display in this column.
	// Must match an exported field name in type T.
	// Required - used with reflection to extract values.
	Field string

	// Width is the column width in characters.
	// Values longer than this will be truncated with "...".
	// Required - should be > 0 for proper layout.
	Width int

	// Sortable indicates if this column can be sorted.
	// When true and table Sortable is true, clicking header sorts by this column.
	// Optional - defaults to false.
	Sortable bool

	// Render is an optional custom rendering function.
	// If provided, it overrides the default field value extraction.
	// Useful for formatting dates, numbers, or complex types.
	// Optional - if nil, uses default fmt.Sprintf("%v", value).
	Render func(T) string
}

TableColumn defines a single column in a table. Each column has a header, field name, width, and optional custom render function.

type TableProps

type TableProps[T any] struct {
	// Data is a reactive reference to the table data.
	// Must be a slice of type T.
	// Required - updates trigger re-renders.
	Data *bubbly.Ref[[]T]

	// Columns defines the table columns to display.
	// Each column specifies header, field, width, and optional render function.
	// Required - should not be empty for usability.
	Columns []TableColumn[T]

	// Sortable enables sorting functionality for the entire table.
	// When true, columns with Sortable=true can be sorted by clicking headers.
	// Clicking a sorted column toggles between ascending/descending.
	// Optional - defaults to false.
	Sortable bool

	// OnRowClick is a callback function executed when a row is clicked.
	// Receives the row data and index as parameters.
	// Optional - if nil, no callback is executed.
	OnRowClick func(T, int)

	// Common props for all components
	CommonProps
}

TableProps defines the configuration properties for a Table component.

Table is a generic component that works with any slice type []T. It displays tabular data with columns, supports row selection, and integrates with the reactive system for dynamic data updates.

Example usage:

type User struct {
    Name   string
    Email  string
    Age    int
}

usersData := bubbly.NewRef([]User{
    {Name: "Alice", Email: "alice@example.com", Age: 30},
    {Name: "Bob", Email: "bob@example.com", Age: 25},
})

table := components.Table(components.TableProps[User]{
    Data: usersData,
    Columns: []components.TableColumn[User]{
        {Header: "Name", Field: "Name", Width: 20},
        {Header: "Email", Field: "Email", Width: 30},
        {Header: "Age", Field: "Age", Width: 10},
    },
    OnRowClick: func(user User, index int) {
        showUserDetails(user)
    },
})

type TabsProps

type TabsProps struct {
	// Tabs is the list of tabs to display.
	// Required - should not be empty.
	Tabs []Tab

	// ActiveIndex is the reactive reference to the currently active tab index.
	// Optional - defaults to 0 (first tab).
	ActiveIndex *bubbly.Ref[int]

	// OnTabChange is called when the active tab changes.
	// Receives the new tab index.
	// Optional - if nil, no callback is executed.
	OnTabChange func(int)

	// Width sets the tabs container width in characters.
	// Default is 60 if not specified.
	Width int

	// CommonProps for styling and identification.
	CommonProps
}

TabsProps defines the properties for the Tabs component. Tabs is an organism component that displays tabbed interface.

type TextAreaProps

type TextAreaProps struct {
	// Value is the reactive reference to the textarea's text content.
	// Required - must be a valid Ref[string].
	// Changes to this ref will update the textarea display.
	// Supports multi-line text with \n for line breaks.
	Value *bubbly.Ref[string]

	// Placeholder is the text displayed when the textarea is empty.
	// Optional - if empty, no placeholder is shown.
	Placeholder string

	// Rows is the height of the textarea in lines.
	// Default: 3 if not specified or <= 0.
	Rows int

	// MaxLength is the maximum number of characters allowed.
	// Optional - if 0, no limit is enforced.
	MaxLength int

	// OnChange is a callback function executed when the text changes.
	// Receives the new text value as a parameter.
	// Optional - if nil, no callback is executed.
	OnChange func(string)

	// Validate is a function to validate the current text.
	// Returns an error if validation fails, nil if valid.
	// Optional - if nil, no validation is performed.
	Validate func(string) error

	// Disabled indicates whether the textarea is disabled.
	// Disabled textareas do not respond to events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// Width sets the width of the textarea in characters.
	// Optional - if 0, defaults to 40 characters.
	Width int

	// NoBorder removes the border if true.
	// Default is false (border is shown).
	// Useful when embedding in other bordered containers.
	NoBorder bool

	// Common props for all components
	CommonProps
}

TextAreaProps defines the configuration properties for a TextArea component.

Example usage:

valueRef := bubbly.NewRef("Enter your\nmulti-line\ntext here")
textarea := components.TextArea(components.TextAreaProps{
    Value:       valueRef,
    Placeholder: "Type something...",
    Rows:        5,
    OnChange: func(value string) {
        fmt.Println("Text changed:", value)
    },
})

type TextProps

type TextProps struct {
	// Content is the text content to display.
	// Required - the main text to render.
	Content string

	// Bold applies bold formatting to the text.
	// Default: false.
	Bold bool

	// Italic applies italic formatting to the text.
	// Default: false.
	Italic bool

	// Underline applies underline formatting to the text.
	// Default: false.
	Underline bool

	// Strikethrough applies strikethrough formatting to the text.
	// Default: false.
	Strikethrough bool

	// Color sets the foreground color of the text.
	// Supports ANSI colors, 256-color palette, and true color.
	// Optional - if not specified, uses theme foreground color.
	Color lipgloss.Color

	// Background sets the background color of the text.
	// Optional - if not specified, uses transparent background.
	Background lipgloss.Color

	// Alignment sets the horizontal alignment of the text.
	// Valid values: AlignLeft, AlignCenter, AlignRight.
	// Only applies when Width is set.
	// Default: AlignLeft.
	Alignment Alignment

	// Width sets the width constraint for the text.
	// When set, text will be aligned according to Alignment.
	// Optional - if 0, text uses natural width.
	Width int

	// Height sets the height constraint for the text.
	// Optional - if 0, text uses natural height.
	Height int

	// Common props for all components
	CommonProps
}

TextProps defines the configuration properties for a Text component.

Example usage:

text := components.Text(components.TextProps{
    Content:   "Hello, World!",
    Bold:      true,
    Color:     lipgloss.Color("63"),
    Alignment: components.AlignCenter,
})

type Theme

type Theme struct {
	// Primary color for main actions and emphasis.
	// Used for: primary buttons, active states, focus indicators.
	Primary lipgloss.Color

	// Secondary color for alternative actions.
	// Used for: secondary buttons, less prominent elements.
	Secondary lipgloss.Color

	// Success color for positive actions and states.
	// Used for: success messages, confirmation buttons, positive indicators.
	Success lipgloss.Color

	// Warning color for caution and attention.
	// Used for: warning messages, caution buttons, alerts.
	Warning lipgloss.Color

	// Danger color for destructive actions and errors.
	// Used for: error messages, delete buttons, critical alerts.
	Danger lipgloss.Color

	// Info color for informational content.
	// Used for: info messages, help text, neutral notifications.
	Info lipgloss.Color

	// Background color for component backgrounds.
	// Used for: panel backgrounds, card backgrounds, modal overlays.
	Background lipgloss.Color

	// Foreground color for primary text.
	// Used for: body text, labels, default content.
	Foreground lipgloss.Color

	// Muted color for secondary text and disabled states.
	// Used for: placeholders, disabled text, subtle elements.
	Muted lipgloss.Color

	// Border style for component borders.
	// Used for: input borders, card borders, dividers.
	Border lipgloss.Border

	// BorderColor for default border color.
	// Used for: neutral borders, dividers, separators.
	BorderColor lipgloss.Color

	// Padding is the default padding value for components.
	// Used for: internal spacing in buttons, cards, panels.
	Padding int

	// Margin is the default margin value for components.
	// Used for: spacing between components.
	Margin int

	// Radius for rounded corners (not directly supported in terminals,
	// but used to select appropriate border styles).
	// 0 = sharp borders, 1 = rounded borders.
	Radius int
}

Theme defines the color scheme and styling properties for all components. It provides a consistent visual language across the component library.

Themes can be customized and provided to components via the BubblyUI composition API's Provide/Inject mechanism:

Setup(func(ctx *bubbly.Context) {
    ctx.Provide("theme", CustomTheme)
})

Components automatically inject the theme and use it for styling.

func GetThemeFromContext

func GetThemeFromContext(ctx *bubbly.Context) Theme

GetThemeFromContext retrieves the theme from component context with fallback to DefaultTheme

func (Theme) GetBorderStyle

func (t Theme) GetBorderStyle() lipgloss.Border

GetBorderStyle returns the appropriate border style based on theme radius. Returns RoundedBorder for radius > 0, NormalBorder otherwise.

func (Theme) GetVariantColor

func (t Theme) GetVariantColor(variant Variant) lipgloss.Color

GetVariantColor returns the appropriate color for a given variant. This helper function maps variant types to theme colors.

type ToggleProps

type ToggleProps struct {
	// Label is the text displayed next to the toggle switch.
	// Optional - if empty, only the toggle indicator is shown.
	Label string

	// Value is the reactive reference to the toggle's on/off state.
	// Required - must be a valid Ref[bool].
	// Changes to this ref will update the toggle display.
	Value *bubbly.Ref[bool]

	// OnChange is a callback function executed when the toggle is switched.
	// Receives the new state as a parameter.
	// Optional - if nil, no callback is executed.
	OnChange func(bool)

	// Disabled indicates whether the toggle is disabled.
	// Disabled toggles do not respond to toggle events and are styled differently.
	// Default: false (enabled).
	Disabled bool

	// Common props for all components
	CommonProps
}

ToggleProps defines the configuration properties for a Toggle component.

Example usage:

valueRef := bubbly.NewRef(false)
toggle := components.Toggle(components.ToggleProps{
    Label: "Enable dark mode",
    Value: valueRef,
    OnChange: func(enabled bool) {
        if enabled {
            activateDarkMode()
        }
    },
})

type ValidateFunc

type ValidateFunc func(value string) error

ValidateFunc is a validation function that returns an error if validation fails.

type Variant

type Variant string

Variant represents a visual variant of a component. Common variants include "primary", "secondary", "danger", "success", "warning".

const (
	// VariantPrimary represents the primary/default variant.
	VariantPrimary Variant = "primary"

	// VariantSecondary represents a secondary/alternative variant.
	VariantSecondary Variant = "secondary"

	// VariantDanger represents a destructive/dangerous action variant.
	VariantDanger Variant = "danger"

	// VariantSuccess represents a successful/positive action variant.
	VariantSuccess Variant = "success"

	// VariantWarning represents a warning/caution variant.
	VariantWarning Variant = "warning"

	// VariantInfo represents an informational variant.
	VariantInfo Variant = "info"
)

Common variant constants used across multiple components.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL