Skip to content

Widgets Selection

mike-ward edited this page May 17, 2026 · 1 revision

Selection Widgets

Selection widgets let users choose one or more values from a fixed set. They all follow the same callback pattern: OnClick or OnSelect receives the new value through the window, state is mutated directly, and the view function reads it on the next frame.


Toggle

gui.Toggle(gui.ToggleCfg{...}) is a latching button — it stays visually pressed when Selected is true.

gui.Toggle(gui.ToggleCfg{
    ID:       "bold",
    IDFocus:  1,
    Label:    "Bold",
    Selected: app.Bold,
    OnClick: func(_ *gui.Layout, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).Bold = !gui.State[App](w).Bold
    },
})

Checkbox

gui.Checkbox(gui.ToggleCfg{...}) uses the same config struct as Toggle but renders as a traditional labelled checkbox.

gui.Checkbox(gui.ToggleCfg{
    ID:       "agree",
    IDFocus:  2,
    Label:    "I agree to the terms",
    Selected: app.Agreed,
    OnClick: func(_ *gui.Layout, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).Agreed = !gui.State[App](w).Agreed
    },
})

Switch

gui.Switch(gui.SwitchCfg{...}) renders as an iOS-style on/off toggle.

gui.Switch(gui.SwitchCfg{
    ID:       "notifications",
    IDFocus:  3,
    Label:    "Enable notifications",
    Selected: app.Notifications,
    OnClick: func(_ *gui.Layout, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).Notifications = !gui.State[App](w).Notifications
    },
})

Radio and RadioButtonGroup

gui.Radio(gui.RadioCfg{...}) is a single radio button. Typically you use several in a column with a shared GroupID so only one can be selected at a time.

for _, opt := range []struct{ label, value string }{
    {"Small", "sm"},
    {"Medium", "md"},
    {"Large", "lg"},
} {
    gui.Radio(gui.RadioCfg{
        ID:       "size-" + opt.value,
        IDFocus:  10,
        GroupID:  "size-group",
        Label:    opt.label,
        Selected: app.Size == opt.value,
        Value:    opt.value,
        OnSelect: func(value string, _ *gui.Event, w *gui.Window) {
            gui.State[App](w).Size = value
        },
    })
}

gui.RadioButtonGroup(gui.RadioButtonGroupCfg{...}) wraps this pattern into a single widget when you want horizontal button-style radio controls.


Select

gui.Select(gui.SelectCfg{...}) is a dropdown that supports single and multi-selection. It has built-in typeahead filtering.

// Single select
gui.Select(gui.SelectCfg{
    ID:          "language",
    Placeholder: "Pick a language",
    Selected:    app.Language,
    Options:     []string{"Go", "Rust", "Zig", "C", "Python"},
    OnSelect: func(sel []string, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).Language = sel
    },
})

// Multi-select — just add SelectMultiple: true
gui.Select(gui.SelectCfg{
    ID:             "tags",
    Placeholder:    "Pick tags",
    Selected:       app.Tags,
    SelectMultiple: true,
    Options:        []string{"backend", "frontend", "mobile", "data"},
    OnSelect: func(sel []string, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).Tags = sel
    },
})

Selected is []string in both cases. For single-select, the slice has at most one element.


Combobox

gui.Combobox(gui.ComboboxCfg{...}) combines a text input with a dropdown list. The user can type to filter options or enter a value not in the list.

gui.Combobox(gui.ComboboxCfg{
    ID:      "city",
    IDFocus: 5,
    Text:    app.City,
    Options: []string{"New York", "London", "Tokyo", "Sydney"},
    OnSelect: func(value string, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).City = value
    },
})

ListBox

gui.ListBox(gui.ListBoxCfg{...}) renders a scrollable list with optional multi-select and drag-to-reorder. Items are typed ListBoxItem values carrying an ID, label, and optional icon.

gui.ListBox(gui.ListBoxCfg{
    ID:          "files",
    IDFocus:     6,
    IDScroll:    100,
    Sizing:      gui.FillFit,
    MaxHeight:   300,
    Data:        app.Files,    // []ListBoxItem
    Reorderable: true,
    OnSelect: func(selected []string, _ *gui.Event, w *gui.Window) {
        gui.State[App](w).SelectedFiles = selected
    },
    OnReorder: func(movedID, beforeID string, w *gui.Window) {
        a := gui.State[App](w)
        from, to := gui.ReorderIndices(itemIDs(a.Files), movedID, beforeID)
        if from >= 0 {
            sliceMove(&a.Files, from, to)
        }
    },
})

IDScroll marks the list as a scrollable container so the framework tracks its scroll position. See Focus and Scrolling for details.

Clone this wiki locally