ImmyGo provides widgets at two levels. This document covers both, with the declarative ui package shown first (recommended) and the lower-level widget package shown second.
import "github.com/amken3d/immygo/ui"
// Create views inline — no pre-declaration needed for stateless views
ui.Text("Hello").Title()
ui.Button("Click").OnClick(fn)
ui.Divider()
// Stateful views must be created outside the build function
name := ui.Input().Placeholder("Name")
toggle := ui.Toggle(false)import "github.com/amken3d/immygo/widget"
// Always declare at package level (state must persist across frames)
var btn = widget.NewButton("Click").WithOnClick(fn)
// Render in layout function
btn.Layout(gtx, th)Renders styled text with the typography scale.
ui.Text("Hello world") // Body text
ui.Text("Page Title").Headline() // Large headline
ui.Text("Section").Title() // Medium headline
ui.Text("Small print").Caption() // Caption
ui.Text("Fine print").Small() // Small body
ui.Text("Important").Bold() // Semi-bold title
ui.Text("Hero").Display() // Largest text
ui.Text("Centered").Center() // Center-aligned
ui.Text("Colored").Color(ui.RGB(255,0,0)) // Custom color
ui.Text("Limited").MaxLines(2) // Truncate after 2 lineswidget.H1("Heading") // HeadlineLarge
widget.H2("Heading") // HeadlineMedium
widget.H3("Heading") // TitleMedium
widget.Body("Text") // BodyMedium
widget.Caption("Small") // LabelSmall
widget.NewLabel("Custom").
WithStyle(widget.LabelHeadline).
WithColor(th.Palette.Primary).
WithAlignment(text.Middle).
WithMaxLines(2).
Layout(gtx, th)A clickable button with six built-in variants.
ui.Button("Primary").OnClick(func() { fmt.Println("clicked") })
ui.Button("Secondary").Secondary()
ui.Button("Outline").Outline()
ui.Button("Text Only").TextButton()
ui.Button("Disabled").Disabled()Buttons are automatically cached by label — safe to create inside the build function.
var saveBtn = widget.NewButton("Save").
WithVariant(widget.ButtonSuccess).
WithCornerRadius(12).
WithMinWidth(120).
WithOnClick(saveHandler)
// In layout:
saveBtn.Layout(gtx, th)| Variant | Declarative | Lower-Level Constant |
|---|---|---|
| Primary (default) | ui.Button("X") |
widget.ButtonPrimary |
| Secondary | .Secondary() |
widget.ButtonSecondary |
| Outline | .Outline() |
widget.ButtonOutline |
| Text | .TextButton() |
widget.ButtonText |
| Danger | — | widget.ButtonDanger |
| Success | — | widget.ButtonSuccess |
Text input fields with placeholder, focus states, and variants.
// Create outside build function (holds text state)
name := ui.Input().Placeholder("Full name")
email := ui.Input().Placeholder("Email").OnChange(func(text string) {
fmt.Println("typed:", text)
})
pass := ui.Password() // Masked input
search := ui.Search() // Rounded search style
multi := ui.Input().MultiLine() // Multi-line
disabled := ui.Input().Disabled()
// Read/write values
fmt.Println(name.Value())
name.SetValue("Alice")var nameField = widget.NewTextField().WithPlaceholder("Name...")
var bioField = widget.NewTextArea().WithPlaceholder("Bio...")
var searchField = widget.NewSearchField()
var passField = widget.NewPasswordField()
// In layout:
nameField.Layout(gtx, th)
// Read value:
nameField.Text()
nameField.SetText("Alice")A Fluent Design switch with smooth knob and track animation.
darkMode := ui.Toggle(false).OnChange(func(on bool) {
fmt.Println("dark mode:", on)
})
// Read: darkMode.Value()
// Write: darkMode.SetValue(true)var darkMode = widget.NewToggle(false).
WithOnChange(func(on bool) { ... })
darkMode.Layout(gtx, th)A checkbox with label and animated check mark.
agreed := ui.Checkbox("I agree to terms", false).
OnChange(func(checked bool) { ... })
// Read: agreed.Value()var agreed = widget.NewCheckbox("I agree", false).
WithOnChange(func(checked bool) { ... })
agreed.Layout(gtx, th)A horizontal range input with drag interaction.
volume := ui.Slider(0, 100, 50).OnChange(func(v float32) {
fmt.Printf("Volume: %.0f\n", v)
})
// Read: volume.Value() (returns actual value between min/max)
// Write: volume.SetValue(75)var slider = widget.NewSlider(0, 100, 50).
WithOnChange(func(v float32) { ... })
slider.Layout(gtx, th)
slider.ActualValue() // current value in min..max rangeMutually exclusive radio buttons with animated selection.
size := ui.RadioGroup("Small", "Medium", "Large").
Selected(1). // pre-select Medium
OnChange(func(index int) {
fmt.Println("Selected:", index)
})
// Read: size.SelectedIndex(), size.SelectedText()var radio = widget.NewRadioGroup("Small", "Medium", "Large").
WithSelected(1).
WithOnChange(func(index int) { ... })
radio.Layout(gtx, th)A selection combo box with popup overlay.
role := ui.Dropdown("Developer", "Designer", "Manager").
Placeholder("Select role").
OnSelect(func(index int, item string) {
fmt.Printf("Selected: %s\n", item)
})
// Read: role.Selected(), role.SelectedText()var dd = widget.NewDropDown("Developer", "Designer", "Manager").
WithPlaceholder("Select role")
dd.Layout(gtx, th)A surface container with elevation (shadow) and rounded corners.
ui.Card(
ui.VStack(
ui.Text("Title").Bold(),
ui.Text("Content goes here."),
).Spacing(8),
).Elevation(2).CornerRadius(12)widget.NewCard().
WithElevation(2).
WithCornerRadius(12).
Child(func(gtx layout.Context) layout.Dimensions {
// card content
}).
Layout(gtx, th)32 built-in vector icons drawn with GPU paths. Scale and color freely.
ui.Icon(ui.IconHome)
ui.Icon(ui.IconSettings).Size(32)
ui.Icon(ui.IconStar).Color(ui.RGB(255, 200, 0))
ui.Icon(ui.IconDelete).OnTap(func() { deleteItem() })IconHome, IconSettings, IconSearch, IconClose, IconAdd, IconRemove, IconEdit, IconDelete, IconCheck, IconChevronLeft, IconChevronRight, IconChevronUp, IconChevronDown, IconMenu, IconUser, IconStar, IconHeart, IconInfo, IconWarning, IconError, IconFolder, IconFile, IconDownload, IconUpload, IconRefresh, IconSend, IconNotification, IconLock, IconUnlock, IconEye, IconEyeOff
widget.NewIcon(widget.IconHome).WithSize(32).WithColor(col).Layout(gtx, th)Small label badges with color variants.
ui.Badge("New") // Primary
ui.Badge("Error").Danger() // Red
ui.Badge("Warning").Warning() // Amber
ui.Badge("Success").Success() // Green
ui.Badge("Tag").Secondary() // Muted
ui.Badge("Removable").OnDismiss(func() { ... }) // With X buttonwidget.NewBadge("New").
WithVariant(widget.BadgeDanger).
WithOnDismiss(func() { ... }).
Layout(gtx, th)A horizontal progress indicator with animated fill.
ui.Progress(0.65) // 65% filled
ui.Progress(0.3).BarHeight(8) // Thicker bar
// Update: progress.SetValue(0.8)var bar = widget.NewProgressBar(0.65).WithHeight(8)
bar.Layout(gtx, th)
bar.Value = 0.8 // updateTabbed navigation with active indicator.
tabs := ui.TabBar("Home", "Profile", "Settings").
OnSelect(func(index int) {
currentTab.Set(index)
})
// Read: tabs.Selected()
// Set: tabs.SetSelected(1)var tabs = widget.NewTabBar("Home", "Profile", "Settings").
WithOnSelect(func(index int) { ... })
tabs.Layout(gtx, th)Scrollable list of selectable items.
list := ui.ListView().
Items("Item 1", "Item 2", "Item 3").
OnSelect(func(index int) { ... })
// With subtitles:
list := ui.ListView().
ItemWithSub("Title", "Subtitle").
ItemWithSub("Another", "Description")var list = widget.NewListView().
AddItem("Title", "Subtitle").
WithOnSelect(func(index int) { ... })
list.Layout(gtx, th)Modal dialogs with OK/Cancel or custom action buttons.
dlg := ui.Dialog("Confirm Delete").
OKText("Delete").
CancelText("Keep")
confirm := ui.Confirm("Are you sure?")
alert := ui.Alert("Operation complete")
// Show/hide
dlg.Show()
dlg.Hide()
// Place at the end of your view tree (renders on top)
ui.VStack(
content,
dlg, // renders as overlay when visible
)var dlg = widget.NewDialog("Confirm").
WithContent(func(gtx layout.Context) layout.Dimensions { ... }).
WithOnResult(func(r widget.DialogResult) { ... })
dlg.Show()
dlg.Layout(gtx, th)Top application bar with title and action buttons.
ui.AppBar("My App").Actions(
ui.Icon(ui.IconSettings).OnTap(openSettings),
)widget.NewAppBar("My App").
WithActions(settingsBtn.Layout).
Layout(gtx, th)Sidebar navigation with collapsible items.
nav := ui.SideNav(
ui.NavItem("Home", "🏠"),
ui.NavItem("Settings", "⚙"),
ui.NavItem("About", "ℹ"),
).OnSelect(func(index int) { page.Set(index) }).
NavWidth(200)var nav = widget.NewSideNav(
widget.NavItem{Label: "Home", Icon: "🏠"},
widget.NavItem{Label: "Settings", Icon: "⚙"},
).WithOnSelect(func(index int) { ... })
nav.Layout(gtx, th)Shows a text tooltip on hover.
ui.Tooltip("Save changes", ui.Icon(ui.IconDownload))
ui.Tooltip("Delete item", ui.Button("Delete"))widget.NewTooltip("Save").
WithChild(func(gtx layout.Context) layout.Dimensions { ... }).
Layout(gtx, th)Render a Go image.Image.
img, _ := png.Decode(file)
ui.Image(img).Size(200, 150).Rounded(8)A horizontal separator line.
ui.Divider()widget.NewDivider().Layout(gtx, th)Flexible or fixed spacing between items.
ui.Spacer() // Flexible — expands to fill
ui.FixedSpacer(16, 0) // Fixed 16dp horizontal
ui.FixedSpacer(0, 20) // Fixed 20dp verticalwidget.NewSpacer(16, 0).Layout(gtx)Stack-based page navigation with animated transitions (slide, fade, slide-up).
nav := ui.Navigator().
Route("home", func() ui.View { return homePage() }).
Route("settings", func() ui.View { return settingsPage() }).
Transition(ui.TransitionSlide)
nav.Push("home") // initial page
nav.Push("settings") // slides in from right
nav.Pop() // slides back to home
nav.Replace("about") // swap without animation
nav.CanPop() // true if stack depth > 1
nav.Current() // "home"var nav = widget.NewNavigator().
WithRoute("home", homeLayout).
WithRoute("settings", settingsLayout).
WithTransition(widget.TransitionSlide).
WithDuration(300 * time.Millisecond)
nav.Push("home")
nav.Layout(gtx, th)A sortable, scrollable data table with column headers and row selection.
grid := ui.DataGrid(
ui.Col("Name"),
ui.ColFixed("Email", 250),
ui.Col("Status"),
).
AddRow("Alice", "alice@example.com", "Active").
AddRow("Bob", "bob@example.com", "Inactive").
OnRowSelect(func(index int) { ... }).
Striped(true)var grid = widget.NewDataGrid(
widget.Column{Header: "Name", Sortable: true},
widget.Column{Header: "Email", Width: 250},
widget.Column{Header: "Status", Sortable: true},
).WithRows(data).WithOnRowSelect(fn).WithStriped(true)
grid.Layout(gtx, th)Expandable hierarchical list with animated expand/collapse.
tree := ui.Tree(
ui.TreeNode("Documents").WithIcon(widget.IconFolder).WithChildren(
ui.TreeNode("readme.txt").WithIcon(widget.IconFile),
ui.TreeNode("notes.txt").WithIcon(widget.IconFile),
).WithExpanded(true),
ui.TreeNode("Images").WithIcon(widget.IconFolder),
).OnSelect(func(node *ui.TreeNodeView) {
fmt.Println("Selected:", node.Label)
})var tree = widget.NewTreeView(
widget.NewTreeNode("Root").WithChildren(
widget.NewTreeNode("Child 1"),
widget.NewTreeNode("Child 2"),
).WithExpanded(true),
).WithOnSelect(fn)
tree.Layout(gtx, th)Vertically stacked collapsible sections with animated height.
acc := ui.Accordion().
SectionExpanded("General", generalContent).
Section("Advanced", advancedContent).
Section("Debug", debugContent).
SingleOpen(true)var acc = widget.NewAccordion().
AddSectionExpanded("General", generalWidget).
AddSection("Advanced", advancedWidget).
WithSingleOpen(true)
acc.Layout(gtx, th)A slide-out overlay panel with scrim and dismiss-on-click-outside.
drawer := ui.Drawer(menuContent).Width(280).RightSide()
drawer.Open()
drawer.Close()
drawer.Toggle()
drawer.IsOpen() // bool
// Place at end of view tree (renders as overlay)
ui.VStack(mainContent, drawer)var drawer = widget.NewDrawer().
WithContent(menuWidget).
WithWidth(280).
WithSide(widget.DrawerRight)
drawer.Open()
drawer.Layout(gtx, th)Non-blocking toast notifications that auto-dismiss.
snack := ui.SnackbarManager()
snack.Show("File saved")
snack.ShowSuccess("Upload complete")
snack.ShowError("Connection failed")
snack.ShowWarning("Low disk space")
snack.ShowWithAction("Deleted item", "Undo", undoFn)
// Place at end of view tree
ui.VStack(content, snack)var snack = widget.NewSnackbar().WithMaxShown(3)
snack.Show("Hello")
snack.Layout(gtx, th)Right-click context menu overlay.
ui.ContextMenu(content,
ui.MenuEntry("Copy", copyFn),
ui.MenuEntryIcon("Delete", ui.IconDelete, deleteFn),
ui.MenuDivider(),
ui.MenuEntry("Properties", propsFn),
)var menu = widget.NewContextMenu(
widget.MenuItem{Label: "Copy", OnClick: copyFn},
widget.MenuSeparator(),
widget.MenuItem{Label: "Delete", Icon: widget.IconDelete, OnClick: deleteFn},
)
// Wrap content
menu.LayoutTrigger(gtx, th, contentWidget)
menu.LayoutOverlay(gtx, th)A date field with calendar popup for date selection.
picker := ui.DatePicker(time.Now()).
OnChange(func(t time.Time) {
fmt.Println("Selected:", t.Format("2006-01-02"))
}).
Placeholder("Choose date...")
// Read: picker.Value()var picker = widget.NewDatePicker(time.Now()).
WithOnChange(func(t time.Time) { ... })
picker.Layout(gtx, th)Multiple styled text spans on a single line.
ui.RichText(
ui.TextSpan("Hello "),
ui.BoldSpan("World"),
ui.ColorSpan("!", ui.RGB(255, 0, 0)),
ui.ItalicSpan(" (italic)"),
ui.SizedSpan("big", 24),
)rt := widget.NewRichText(
widget.Span("Normal "),
widget.BoldSpan("Bold "),
widget.ColorSpan("Red", color.NRGBA{R: 255, A: 255}),
)
rt.Layout(gtx, th)A Node-RED-style flow-graph editor with pan/zoom, drag, multi-select, marquee, snap-to-grid, save/load, typed ports, and live ImmyGo widgets inside node bodies. Significant enough to deserve its own guide — see Node Canvas. Quick reference:
graph := &widget.Graph{
Nodes: []widget.Node{
{ID: "a", Title: "Source", X: 60, Y: 80,
Outputs: []widget.Port{{Name: "out"}}},
{ID: "b", Title: "Sink", X: 320, Y: 80,
Inputs: []widget.Port{{Name: "in"}}},
},
Edges: []widget.Edge{{From: "a", FromPort: 0, To: "b", ToPort: 0}},
}
canvas := ui.Canvas(graph)
ui.Run("Flow", func() ui.View { return canvas })Companion APIs:
widget.NewCatalog()/Register(NodeDef)— register node types so users can spawn them from a palette and so the graph can round-trip via JSON.ui.NodePalette(catalog, onAdd)— sidebar of buttons, one per registered type.ui.NodeBody(func() ui.View)— adapt any ImmyGo view as a node body. Inner widgets (sliders, inputs, toggles) get pointer events normally; users drag the node from the header.ui.PortLegend(catalog)— color-coded key for typed ports.widget.MarshalGraph/UnmarshalGraph— JSON persistence; bodies rebuild from the catalog on load.
Generate a UI view from a natural language description at runtime. Useful for rapid exploration and prototyping.
// Generate a view from description (async, shows placeholder while generating)
ui.Prototype("a login form with email and password fields")
// With modifiers
ui.Prototype("a settings panel").Padding(16).MaxWidth(600)
// Eject the generated Go source code
proto := ui.Prototype("a dashboard with stats cards")
proto.Eject() // prints Go code to stdoutThe prototype widget:
- Shows "Generating..." on first render
- Calls the AI to generate a JSON widget tree
- Maps the JSON to real
uiviews (VStack, HStack, Text, Button, Input, Card, etc.) - Also stores the equivalent Go source code for ejection
Supported JSON widget types: VStack, HStack, Text, Button, Input, Card, Spacer, Divider, Toggle, Checkbox, Progress.
Unknown types render as [unsupported: TypeName].