Writing components
Unstyled components
NSGo's own controls without a look, to style part by part or to build a styled library on.
base holds every control ui has, with its behavior, state and accessibility but no look. Each part takes a Class, a Style and a StyleFunc (Parts and states), so a library or an app with a design of its own uses them instead of ui, or wraps them as ui does (Looks and themes). This page covers the controls of the component reference; the gallery shows every one beside its ui form.
Button
base.Button takes its look from Style and its content from Children. OnPress runs on a completed press; OnPressDown and OnPressUp follow a press starting and ending, for feedback of your own.
base.Button{
Label: "Save",
OnPress: func(core.PressEvent) { save() },
Style: core.Style{Background: core.RGB(0x007aff), Padding: core.Symmetric(8, 14), CornerRadius: 8, AlignSelf: core.AlignStart,
Active: &core.Style{Opacity: core.Alpha(0.85)}},
Children: []core.Node{core.Text{Content: "Save", Style: core.Style{Color: core.RGB(0xffffff)}}},
}
Disabled, Selected and Expanded report a state to the app, the style and assistive technology: a toggle or the current item of a list uses Selected, a disclosure Expanded. NoFocus takes a button out of the Tab stops when a row or a list reaches it another way.
Checkbox
base.Checkbox takes a style for the box, one for the indicator and your mark inside it, shown from the value the app owns.
base.Checkbox{
Checked: done.Get,
OnChange: done.Set,
Label: "Remember me",
Style: core.Style{Direction: core.Row, AlignItems: core.AlignCenter, Gap: 8},
IndicatorStyle: core.Style{Width: core.Pt(18), Height: core.Pt(18), BorderWidth: 1, CornerRadius: 5},
Indicator: core.Icon{SVG: lucide.Check(), Style: core.Style{FontSize: 14}},
Children: []core.Node{core.Text{Content: "Remember me"}},
}
The indicator takes its state from the box through PartStates, so checked: classes style it without making it a second stop.
Switch
base.Switch is a track, a thumb inside it, then its children as the label. A press reports the value it asks for, the opposite of Checked.
base.Switch{
Checked: dark.Get,
OnChange: dark.Set,
Label: "Dark appearance",
Style: core.Style{Direction: core.Row, AlignItems: core.AlignCenter, Gap: 8},
TrackStyle: core.Style{Width: core.Pt(38), Height: core.Pt(22), CornerRadius: 11},
ThumbStyle: core.Style{Width: core.Pt(18), Height: core.Pt(18), CornerRadius: 9},
}
The track and thumb take StyleFuncs of their own, and their state styles follow Checked.
Slider
base.Slider takes a base.SliderValue: base.SingleValue(v) or base.RangeValue(low, high). It styles the track, the indicator, the range and the thumbs apart.
level := core.NewSignal(cx, 3.0)
base.Slider{
Value: func() base.SliderValue { return base.SingleValue(level.Get()) },
OnChange: func(v base.SliderValue) { level.Set(v.End) },
Min: func() float64 { return 0 },
Max: func() float64 { return 10 },
Step: func() float64 { return 1 },
Label: "Level",
Style: core.Style{Height: core.Pt(28)},
TrackStyle: core.Style{Height: core.Pt(4), CornerRadius: 2},
ThumbStyle: core.Style{Width: core.Pt(18), Height: core.Pt(18), CornerRadius: 9},
}
Axis makes it vertical, Marks places your own ticks on the indicator, and OnChangeEnd reports the value once a drag stops.
Select
base.Select takes base.SelectItems and a *string value, so “none” is nil. You style the trigger, the popup, its rows and its groups.
choice := core.NewSignal[*string](cx, nil)
base.Select{
Items: func() []base.SelectItem {
return []base.SelectItem{{Value: "s", Title: "Small"}, {Value: "m", Title: "Medium"}}
},
Value: choice.Get,
OnChange: choice.Set,
Label: "Size",
Style: core.Style{Height: core.Pt(28), Padding: core.Symmetric(4, 10), BorderWidth: 1, CornerRadius: 6},
PopupStyle: core.Style{Padding: core.All(4), CornerRadius: 8},
ItemStyle: core.Style{Padding: core.Symmetric(4, 8), CornerRadius: 4},
}
Adjacent items with the same Group share a header, typeahead matches the items’ titles, SearchField draws a search field of your own, and Cleanable shows a clear button. OnCancel hears Escape, back and a press outside.
Tabs
base.Tabs holds base.Tabs and draws the indicator from IndicatorStyle; the app keeps which tab is selected. base.Tab.OnPress runs on every press, the selected one’s included.
base.Tabs{Label: "Sections", IndicatorStyle: core.Style{Height: core.Pt(2), Background: core.RGB(0x007aff)}, Children: []core.Node{
base.Tab{Label: "Overview", Selected: func() bool { return section.Get() == "overview" },
OnPress: func(core.PressEvent) { section.Set("overview") }, Children: []core.Node{core.Text{Content: "Overview"}}},
base.Tab{Label: "Activity", Selected: func() bool { return section.Get() == "activity" },
OnPress: func(core.PressEvent) { section.Set("activity") }, Children: []core.Node{core.Text{Content: "Activity"}}},
}}
Dialog
base.Dialog takes its backdrop, layer and popup styles, an Animation or a TransitionShader, and your children. Open, DefaultOpen and OnOpenChange control it; OnOpen, OnClose and OnCancel report what happened. NoCloseOnSwipe keeps iOS’s swipe from closing it, Presentation chooses the iOS presentation, and StatusBar changes the bar while it is open.
base.Sheet is a panel at any edge, with a backdrop and your content; with CloseOnSwipe a bottom sheet follows a downward drag and closes past a threshold. base.ShowAlertDialog shows the system’s alert or action sheet with buttons by role:
base.ShowAlertDialog(cx, base.AlertDialog{
Title: "Delete the note?",
Actions: []core.AlertAction{
{Label: "Cancel", Role: core.AlertCancel},
{Label: "Delete", Role: core.AlertDestructive},
},
}, func(label string) {
if label == "Delete" {
remove()
}
})
Menu
base.Menu opens base.MenuItems from a trigger of your own, and base.ContextMenu at the pointer. An item reports OnSelect and closes the menu unless NoCloseOnSelect; Checked makes it a checkable item, Indicator draws your mark before its content, and Role chooses what assistive technology hears: a menu item, a checkbox or a radio item. A base.Submenu holds more items.
base.Menu{Trigger: core.Text{Content: "Actions"}, Children: []core.Node{
base.MenuItem{Label: "Show sidebar", Checked: sidebar.Get, OnSelect: func() { sidebar.Set(!sidebar.Get()) }},
base.Submenu{Label: "Share", Children: []core.Node{
base.MenuItem{Label: "Copy link", OnSelect: copyLink},
base.MenuItem{Label: "Email", OnSelect: emailLink},
}},
}}
Placement, Align, Offset and Margin place the popup on desktops, and PopupStyle and BackdropStyle style it.