Complete API reference for the React Hooks-style Go CLI/TUI framework
- Entry Function
- Component Context (C)
- Hooks
- Nodes
- Built-in Components
- Styling System
- Colors and Borders
- Key Constants
- Mouse Event Types
Start a Rego application.
func Run(root func(C) Node) errorParameters:
root- Root component function
Returns:
error- Runtime error, nil on normal exit
Example:
func main() {
if err := rego.Run(App); err != nil {
log.Fatal(err)
}
}
func App(c rego.C) rego.Node {
return rego.Text("Hello, Rego!")
}C is the component context interface, the first parameter of every component function.
type C interface {
// Child gets a child component context
Child(key string, index ...int) C
// Refresh manually triggers a re-render
Refresh()
// Quit exits the application
Quit()
// SetCursor sets cursor position (for IME input)
SetCursor(x, y int)
// Wrap wraps a node to track its position (for mouse events)
Wrap(node Node) *componentNode
// Rect gets the component's screen area
Rect() Rect
}Creates a child component context with isolated state space.
func (c C) Child(key string, index ...int) CParameters:
key- Child component identifierindex- Optional index (for list scenarios)
Example:
func App(c rego.C) rego.Node {
return rego.VStack(
Header(c.Child("header")),
Content(c.Child("content")),
// Use index in lists
rego.For(items, func(item Item, i int) rego.Node {
return ItemRow(c.Child("item", i), item)
}),
)
}Manually triggers a UI re-render.
func (c C) Refresh()Use Cases:
- After updating state in a goroutine
- After async operations complete
Example:
rego.UseEffect(c, func() func() {
go func() {
data := fetchData()
state.Set(data)
c.Refresh() // Refresh after async update
}()
return nil
})Exits the application.
func (c C) Quit()Example:
rego.UseKey(c, func(key rego.Key, r rune) {
if r == 'q' || key == rego.KeyCtrlC {
c.Quit()
}
})Gets the component's position and size on screen.
func (c C) Rect() Rect
type Rect struct {
X, Y, W, H int
}
func (r Rect) Contains(x, y int) boolExample:
rego.UseMouse(c, func(ev rego.MouseEvent) {
rect := c.Rect()
if rect.Contains(ev.X, ev.Y) {
// Mouse is within component area
}
})Declares a state variable.
func Use[T any](c C, key string, initial T) *State[T]Parameters:
c- Component contextkey- State identifier (unique within component)initial- Initial value
Returns:
*State[T]- State object
State[T] Methods:
type State[T any] struct {
Val T // Current value
}
// Set sets a new value and triggers re-render
func (s *State[T]) Set(value T)
// Update uses a function to update the value
func (s *State[T]) Update(fn func(old T) T)Example:
func Counter(c rego.C) rego.Node {
// Declare different types of state
count := rego.Use(c, "count", 0)
name := rego.Use(c, "name", "")
items := rego.Use(c, "items", []string{})
// Read value
fmt.Println(count.Val)
// Set value
count.Set(10)
// Functional update
count.Update(func(v int) int { return v + 1 })
items.Update(func(list []string) []string {
return append(list, "new item")
})
return rego.Text(fmt.Sprintf("Count: %d", count.Val))
}Use in conditionals/loops (unlike React, Rego supports this):
if showExtra {
extra := rego.Use(c, "extra", "") // ✅ Completely legal
}
for i := 0; i < 3; i++ {
item := rego.Use(c, fmt.Sprintf("item-%d", i), "") // ✅ Legal
}Declares a side effect with optional cleanup function.
func UseEffect(c C, fn func() func(), deps ...any)Parameters:
c- Component contextfn- Effect function, returns cleanup function or nildeps- Dependency list, effect re-runs when deps change
Execution Rules:
- Runs on first render
- When deps change: calls previous cleanup, then runs new effect
- Calls cleanup on component unmount
- With no deps: only runs on first render
Example:
// Timer
func Timer(c rego.C) rego.Node {
seconds := rego.Use(c, "seconds", 0)
rego.UseEffect(c, func() func() {
ticker := time.NewTicker(time.Second)
go func() {
for range ticker.C {
seconds.Update(func(v int) int { return v + 1 })
c.Refresh()
}
}()
return ticker.Stop // Cleanup function
}) // No deps = runs once
return rego.Text(fmt.Sprintf("%d seconds", seconds.Val))
}
// Data fetching (re-fetches when deps change)
func UserProfile(c rego.C, userId string) rego.Node {
user := rego.Use(c, "user", User{})
loading := rego.Use(c, "loading", true)
rego.UseEffect(c, func() func() {
loading.Set(true)
go func() {
data := fetchUser(userId)
user.Set(data)
loading.Set(false)
c.Refresh()
}()
return nil // No cleanup needed
}, userId) // Re-runs when userId changes
if loading.Val {
return rego.Spinner(c.Child("spin"), "Loading...")
}
return rego.Text(user.Val.Name)
}Registers a keyboard event handler.
func UseKey(c C, handler func(key Key, r rune))Parameters:
c- Component contexthandler- Event handler functionkey- Special keys (arrows, enter, etc.)r- Character (for regular key presses)
Example:
rego.UseKey(c, func(key rego.Key, r rune) {
// Handle special keys
switch key {
case rego.KeyUp:
cursor.Update(func(v int) int { return max(0, v-1) })
case rego.KeyDown:
cursor.Update(func(v int) int { return v + 1 })
case rego.KeyEnter:
submit()
case rego.KeyBackspace:
deleteChar()
}
// Handle character keys
switch r {
case 'q':
c.Quit()
case '+':
count.Set(count.Val + 1)
}
})Registers a mouse event handler.
func UseMouse(c C, handler func(ev MouseEvent))MouseEvent Structure:
type MouseEvent struct {
X, Y int
Button MouseButton
Type MouseEventType
}
type MouseEventType int
const (
MouseEventPress MouseEventType = iota
MouseEventRelease
MouseEventClick
MouseEventMove
MouseEventScrollUp
MouseEventScrollDown
)
type MouseButton int
const (
MouseButtonNone MouseButton = iota
MouseButtonLeft
MouseButtonMiddle
MouseButtonRight
)Example:
rego.UseMouse(c, func(ev rego.MouseEvent) {
rect := c.Rect()
switch ev.Type {
case rego.MouseEventClick:
if ev.Button == rego.MouseButtonLeft && rect.Contains(ev.X, ev.Y) {
onClick()
}
case rego.MouseEventScrollUp:
scrollOffset.Update(func(v int) int { return max(0, v-1) })
case rego.MouseEventScrollDown:
scrollOffset.Update(func(v int) int { return v + 1 })
}
})Declares a component as focusable.
func UseFocus(c C) FocusStateFocusState Structure:
type FocusState struct {
IsFocused bool // Whether currently focused
Focus func() // Request focus
Blur func() // Release focus
}Focus Navigation:
Tab- Switch to next focusable componentShift+Tab- Switch to previous focusable component- Mouse click auto-focuses
Example:
func InputPanel(c rego.C) rego.Node {
value := rego.Use(c, "value", "")
focus := rego.UseFocus(c)
rego.UseKey(c, func(key rego.Key, r rune) {
if !focus.IsFocused {
return // Only handle input when focused
}
if r != 0 {
value.Set(value.Val + string(r))
}
})
// Style based on focus state
borderColor := rego.Gray
if focus.IsFocused {
borderColor = rego.Cyan
}
return c.Wrap(rego.Box(
rego.Text(value.Val),
).Border(rego.BorderSingle).BorderColor(borderColor))
}Caches computed values, only recomputes when deps change.
func UseMemo[T any](c C, fn func() T, deps ...any) TParameters:
c- Component contextfn- Computation functiondeps- Dependency list
Returns:
- Computed result
Example:
func FilteredList(c rego.C, items []Item, filter string) rego.Node {
// Only recomputes when items or filter change
filtered := rego.UseMemo(c, func() []Item {
result := make([]Item, 0)
for _, item := range items {
if strings.Contains(item.Name, filter) {
result = append(result, item)
}
}
return result
}, items, filter)
return rego.For(filtered, func(item Item, i int) rego.Node {
return rego.Text(item.Name)
})
}Creates a mutable reference, solves closure traps.
func UseRef[T any](c C, initial T) *Ref[T]
type Ref[T any] struct {
Current T
}Characteristics:
- Modifying
Ref.Currentdoesn't trigger re-render - Always accesses the latest value in closures
Example:
func StreamingOutput(c rego.C) rego.Node {
output := rego.Use(c, "output", "")
// Ref points to the latest output pointer
outputRef := rego.UseRef(c, &output.Val)
rego.UseEffect(c, func() func() {
go func() {
for chunk := range stream {
// Access latest value via Ref, avoiding stale closure
*outputRef.Current += chunk
c.Refresh()
}
}()
return nil
})
return rego.Text(output.Val)
}Reads context values provided by ancestor components.
// Create context
func CreateContext[T any](name string) Context[T]
// Read context value
func UseContext[T any](c C, ctx Context[T]) T
// Provide context value (node)
func Provide[T any](ctx Context[T], value T, children ...Node) NodeExample:
// Define theme context
var ThemeContext = rego.CreateContext[string]("theme")
// Root component provides value
func App(c rego.C) rego.Node {
theme := rego.Use(c, "theme", "dark")
return rego.Provide(ThemeContext, theme.Val,
rego.VStack(
Header(c.Child("header")),
Content(c.Child("content")),
),
)
}
// Child component consumes value
func ThemedButton(c rego.C, text string) rego.Node {
theme := rego.UseContext(c, ThemeContext)
if theme == "dark" {
return rego.Text(text).Color(rego.White).Background(rego.Black)
}
return rego.Text(text).Color(rego.Black).Background(rego.White)
}Creates a bidirectional communication bridge between UI and background Agent.
func UseBridge[S any, Q any, A any](c C, initial S) *Bridge[S, Q, A]Type Parameters:
S- State typeQ- Question type (Agent requests from UI)A- Answer type (UI replies to Agent)
Bridge Methods:
type Bridge[S, Q, A any] struct { ... }
// State gets current state
func (b *Bridge[S, Q, A]) State() S
// HasInteraction checks for pending interaction request
func (b *Bridge[S, Q, A]) HasInteraction() bool
// Interaction gets current interaction request
func (b *Bridge[S, Q, A]) Interaction() Q
// Submit submits answer, unblocking Agent
func (b *Bridge[S, Q, A]) Submit(answer A)
// Handle gets the Agent-side handle
func (b *Bridge[S, Q, A]) Handle() Handle[S, Q, A]Handle Interface (Agent-side):
type Handle[S, Q, A any] interface {
Update(state S) // Update state (non-blocking)
Ask(question Q) A // Request user interaction (blocking)
}Example:
type AgentState struct {
Response string
Status string
}
type Confirm struct {
Message string
}
func AgentUI(c rego.C) rego.Node {
bridge := rego.UseBridge[AgentState, Confirm, bool](c, AgentState{})
rego.UseEffect(c, func() func() {
go func() {
handle := bridge.Handle()
// Streaming updates
for token := range stream {
handle.Update(AgentState{
Response: current + token,
Status: "streaming",
})
}
// Request user confirmation (blocking)
confirmed := handle.Ask(Confirm{Message: "Apply changes?"})
if confirmed {
applyChanges()
}
}()
return nil
})
return rego.VStack(
rego.Markdown(bridge.State().Response),
rego.When(bridge.HasInteraction(),
rego.HStack(
rego.Text(bridge.Interaction().Message),
rego.Button(c.Child("yes"), rego.ButtonProps{
Label: "Yes",
OnClick: func() { bridge.Submit(true) },
}),
rego.Button(c.Child("no"), rego.ButtonProps{
Label: "No",
OnClick: func() { bridge.Submit(false) },
}),
),
),
)
}Creates a text node.
func Text(content string) *textNodeStyle Methods:
text := rego.Text("Hello").
Bold(). // Bold
Italic(). // Italic
Underline(). // Underline
Dim(). // Dimmed
Blink(). // Blinking
Color(rego.Cyan). // Foreground color
Background(rego.Black). // Background color
Wrap(true) // Word wrapCreates an empty node that takes no space.
func Empty() *emptyNodeCreates a flexible spacer that fills remaining space.
func Spacer() *spacerNodeExample:
rego.HStack(
rego.Text("Left"),
rego.Spacer(), // Fills middle space
rego.Text("Right"),
)Creates a horizontal divider line.
func Divider() *dividerNode
divider := rego.Divider().
Char('═'). // Set divider character
Color(rego.Gray) // Set colorMarks cursor position (for IME input positioning).
func Cursor(c C) NodeVertical stack layout.
func VStack(children ...Node) *vstackNodeStyle Methods:
rego.VStack(children...).
Gap(1). // Gap between children
Justify(rego.AlignCenter). // Main axis alignment (Top/Center/Bottom)
Padding(1, 2). // Padding (vertical, horizontal)
Height(10). // Fixed height
Flex(1). // Flex weight
Background(rego.Black) // Background colorHorizontal stack layout.
func HStack(children ...Node) *hstackNodeStyle Methods:
rego.HStack(children...).
Gap(2). // Gap between children
Justify(rego.AlignRight). // Main axis alignment (Left/Center/Right)
Padding(0, 1). // Padding
Height(1). // Fixed height
Flex(1) // Flex weightContainer node with border and padding support.
func Box(child Node) *boxNodeStyle Methods:
rego.Box(child).
Border(rego.BorderRounded). // Border style
BorderColor(rego.Cyan). // Border color
Padding(1, 2). // Padding (vertical, horizontal)
PaddingAll(1, 2, 1, 2). // Padding (top, right, bottom, left)
Width(40). // Fixed width
Height(10). // Fixed height
Flex(1). // Flex weight
Valign(rego.AlignCenter). // Vertical alignment
Background(rego.Black) // Background colorCentering helper component.
func Center(child Node) NodeExample:
rego.Center(
rego.Box(
rego.Text("Centered Content"),
).Border(rego.BorderSingle),
)Conditional rendering.
func When(condition bool, node Node) *whenNodeExample:
rego.When(loading, rego.Spinner(c.Child("spin"), "Loading..."))
rego.When(error != nil, rego.Text(error.Error()).Color(rego.Red))Conditional rendering with else branch.
func WhenElse(condition bool, trueNode, falseNode Node) *whenElseNodeExample:
rego.WhenElse(loggedIn,
UserPanel(c.Child("user")),
LoginForm(c.Child("login")),
)List rendering.
func For[T any](items []T, render func(item T, index int) Node) NodeExample:
rego.For(todos, func(todo Todo, i int) rego.Node {
return TodoItem(c.Child("todo", i), todo, i == selected)
})Scrollable container.
func ScrollBox(c C, child Node) *componentNodeFeatures:
- Mouse wheel support
- Auto-displays scrollbar
Auto-scroll to bottom container (ideal for logs/chat).
func TailBox(c C, child Node) *componentNodeFeatures:
- New content auto-scrolls to bottom
- Manual scroll up pauses auto-scroll
Button component.
type ButtonProps struct {
Label string // Button text
OnClick func() // Click callback
Primary bool // Whether primary button
}
func Button(c C, props ButtonProps) NodeText input component.
type TextInputProps struct {
Value string // Current value
Placeholder string // Placeholder
Label string // Label
Width int // Width
Height int // Height (for multiline)
Multiline bool // Whether multiline
Password bool // Whether password mode
OnChanged func(string) // Value change callback
OnSubmit func(string) // Enter submit callback
}
func TextInput(c C, props TextInputProps) NodeCheckbox component.
type CheckboxProps struct {
Label string // Label
Checked bool // Whether checked
OnChanged func(bool) // State change callback
}
func Checkbox(c C, props CheckboxProps) NodeLoading animation component.
func Spinner(c C, label string) NodeMarkdown rendering component.
func Markdown(content string) *markdownNode
md := rego.Markdown(content).
Theme("dark") // Theme: "dark", "light", "notty"Reusable style object.
style := rego.NewStyle().
Foreground(rego.White).
Background(rego.Black).
Bold().
Padding(1, 2).
Border(rego.BorderSingle)
rego.Text("Hello").Apply(style)
rego.Box(child).Apply(style)type Align int
const (
AlignLeft Align = iota // Left / Top alignment
AlignCenter // Center alignment
AlignRight // Right / Bottom alignment
)const (
Default Color = iota // Terminal default
Black
Red
Green
Yellow
Blue
Magenta
Cyan
White
Gray
)const (
BorderNone BorderStyle = iota // No border
BorderSingle // ┌─┐
BorderDouble // ╔═╗
BorderRounded // ╭─╮
BorderThick // ┏━┓
)const (
KeyNone Key = iota
KeyUp
KeyDown
KeyLeft
KeyRight
KeyEnter
KeyEscape
KeyTab
KeyBackspace
KeyDelete
KeyHome
KeyEnd
KeyPgUp
KeyPgDown
KeyInsert
KeyF1
KeyF2
// ... F3-F12
KeyCtrlA
KeyCtrlB
KeyCtrlC
// ... Ctrl+D-Z
)type MouseEventType int
const (
MouseEventPress MouseEventType = iota // Press
MouseEventRelease // Release
MouseEventClick // Click
MouseEventMove // Move
MouseEventScrollUp // Scroll up
MouseEventScrollDown // Scroll down
)
type MouseButton int
const (
MouseButtonNone MouseButton = iota // No button
MouseButtonLeft // Left button
MouseButtonMiddle // Middle button
MouseButtonRight // Right button
)Rego provides a testing package rego/testing:
import regotest "github.com/erweixin/rego/testing"
func TestMyComponent(t *testing.T) {
// Create mock screen
screen := regotest.NewMockScreen(80, 24)
// Create test runtime
rt := regotest.NewTestRuntime(MyComponent, 80, 24)
// Execute render
rt.Render()
// Get screen content
content := rt.Screen.GetContentString()
// Simulate key press
rt.DispatchKey(rego.KeyEnter, 0, 0)
// Assert results
if !strings.Contains(content, "expected text") {
t.Error("Expected text not found")
}
}| Hook | Signature | Description |
|---|---|---|
Use |
Use[T](c, key, initial) *State[T] |
State management |
UseEffect |
UseEffect(c, fn, deps...) |
Side effects |
UseKey |
UseKey(c, handler) |
Keyboard events |
UseMouse |
UseMouse(c, handler) |
Mouse events |
UseFocus |
UseFocus(c) FocusState |
Focus management |
UseMemo |
UseMemo[T](c, fn, deps...) T |
Memoization |
UseRef |
UseRef[T](c, initial) *Ref[T] |
References |
UseContext |
UseContext[T](c, ctx) T |
Context consumption |
UseBridge |
UseBridge[S,Q,A](c, init) *Bridge |
Agent communication |
| Category | APIs |
|---|---|
| Basic | Text, Empty, Spacer, Divider, Cursor |
| Layout | VStack, HStack, Box, Center |
| Control | When, WhenElse, For |
| Scroll | ScrollBox, TailBox |
| Components | Button, TextInput, Checkbox, Spinner, Markdown |
| Method | Description |
|---|---|
c.Child(key, i...) |
Get child component context |
c.Refresh() |
Trigger re-render |
c.Quit() |
Exit application |
c.Rect() |
Get component area |
c.Wrap(node) |
Wrap node |
This documentation is based on Rego v0.1.0-beta