Form widgets allow user input and data collection.
Text input field with Material or Cupertino styling.
textField := material.NewTextField()
textField.Decoration = &material.InputDecoration{
LabelText: "Email",
HintText: "Enter your email",
HelperText: "We'll never share your email",
}
textField.OnChanged = func(text string) {
fmt.Println("Text changed:", text)
}textField := cupertino.NewCupertinoTextField()
textField.Placeholder = "Enter your email"
textField.OnChanged = func(text string) {
fmt.Println("Text changed:", text)
}Material TextField:
Controller- Text editing controller with signalDecoration- Input decoration (label, hint, icons)KeyboardType- Type of keyboard to showMaxLines- Number of lines (1 for single-line)ObscureText- Hide text (for passwords)Enabled- Whether field is enabledOnChanged- Called when text changesOnSubmitted- Called when user submits
Cupertino TextField:
Controller- Text editing controllerPlaceholder- Placeholder textPrefix- Leading widgetSuffix- Trailing widgetObscureText- Hide text (for passwords)OnChanged- Called when text changes
Manage text field value with signals:
controller := material.NewTextEditingController("Initial text")
// Read current value
currentText := controller.Text.Get()
// Update value programmatically
controller.Text.Set("New text")
// React to changes
signals.NewEffect(func() {
fmt.Println("Text is:", controller.Text.Get())
})Customize Material TextField appearance:
&material.InputDecoration{
Label Text: "Username",
HintText: "johndoe",
HelperText: "Your unique username",
ErrorText: "", // Set to show error
PrefixIcon: widgets.NewIcon(widgets.IconPerson),
SuffixIcon: widgets.NewIcon(widgets.IconCheck),
Border: material.InputBorderOutline,
FillColor: goflow.NewColor(245, 245, 245, 255),
Filled: true,
}passwordField := material.NewTextField()
passwordField.ObscureText = true
passwordField.Decoration = &material.InputDecoration{
LabelText: "Password",
PrefixIcon: widgets.NewIcon(widgets.IconLock),
Border: material.InputBorderOutline,
}email := signals.New("")
emailValid := signals.NewComputed(func() bool {
e := email.Get()
return strings.Contains(e, "@")
})
emailField := material.NewTextField()
emailField.Controller.Text = email
emailField.Decoration = &material.InputDecoration{
LabelText: "Email",
ErrorText: func() string {
if !emailValid.Get() && email.Get() != "" {
return "Invalid email"
}
return ""
}(),
}Material Design checkbox.
checked := signals.New(false)
checkbox := material.NewCheckbox(checked.Get(), func(value bool) {
checked.Set(value)
fmt.Println("Checkbox:", value)
})Value- Current state (true/false)OnChanged- Callback when toggledActiveColor- Color when checkedCheckColor- Color of checkmarkTristate- Allow null/indeterminate state
&widgets.Row{
Children: []goflow.Widget{
material.NewCheckbox(isChecked, onChanged),
&widgets.Text{Data: "I agree to the terms"},
},
}termsAccepted := signals.New(false)
&widgets.Row{
Children: []goflow.Widget{
material.NewCheckbox(termsAccepted.Get(), func(value bool) {
termsAccepted.Set(value)
}),
&widgets.Text{Data: "I accept the terms and conditions"},
},
}
// Somewhere else in UI
submitButton := material.NewButton(
&widgets.Text{Data: "Submit"},
func() {
if !termsAccepted.Get() {
showError("Please accept terms")
return
}
submit()
},
)Material Design radio button.
selectedOption := signals.New("option1")
&widgets.Column{
Children: []goflow.Widget{
&widgets.Row{
Children: []goflow.Widget{
material.NewRadio("option1", selectedOption.Get(), func(value interface{}) {
selectedOption.Set(value.(string))
}),
&widgets.Text{Data: "Option 1"},
},
},
&widgets.Row{
Children: []goflow.Widget{
material.NewRadio("option2", selectedOption.Get(), func(value interface{}) {
selectedOption.Set(value.(string))
}),
&widgets.Text{Data: "Option 2"},
},
},
},
}Value- Value of this radio buttonGroupValue- Currently selected value in groupOnChanged- Callback when selectedActiveColor- Color when selected
type RadioOption struct {
Value string
Label string
}
func buildRadioGroup(options []RadioOption, selectedValue *signals.Signal[string]) goflow.Widget {
children := make([]goflow.Widget, len(options))
for i, opt := range options {
option := opt // Capture for closure
children[i] = &widgets.Row{
Children: []goflow.Widget{
material.NewRadio(
option.Value,
selectedValue.Get(),
func(value interface{}) {
selectedValue.Set(value.(string))
},
),
&widgets.Text{Data: option.Label},
},
}
}
return &widgets.Column{Children: children}
}
// Usage
gender := signals.New("male")
buildRadioGroup([]RadioOption{
{Value: "male", Label: "Male"},
{Value: "female", Label: "Female"},
{Value: "other", Label: "Other"},
}, gender)Toggle switch with Material or Cupertino styling.
enabled := signals.New(true)
switchWidget := material.NewSwitch(enabled.Get(), func(value bool) {
enabled.Set(value)
})enabled := signals.New(true)
switchWidget := cupertino.NewCupertinoSwitch(enabled.Get(), func(value bool) {
enabled.Set(value)
})Material Switch:
Value- Current stateOnChanged- Callback when toggledActiveColor- Color when onActiveTrackColor- Track color when onInactiveThumbColor- Thumb color when offInactiveTrackColor- Track color when off
Cupertino Switch:
Value- Current stateOnChanged- Callback when toggledActiveColor- Color when on (defaults to iOS blue)
notifications := signals.New(true)
material.NewListTile(&widgets.Row{
Children: []goflow.Widget{
&widgets.Expanded{
Child: &widgets.Column{
Children: []goflow.Widget{
&widgets.Text{Data: "Notifications"},
&widgets.Text{Data: "Receive push notifications"},
},
},
},
material.NewSwitch(notifications.Get(), func(value bool) {
notifications.Set(value)
}),
},
})Value slider with Material or Cupertino styling.
volume := signals.New(50.0)
slider := material.NewSlider(volume.Get(), 0, 100, func(value float64) {
volume.Set(value)
})brightness := signals.New(0.5)
slider := cupertino.NewCupertinoSlider(brightness.Get(), 0, 1, func(value float64) {
brightness.Set(value)
})Material Slider:
Value- Current valueMin- Minimum valueMax- Maximum valueDivisions- Number of discrete divisions (optional)OnChanged- Called when value changesActiveColor- Color of active trackInactiveColor- Color of inactive trackLabel- Label shown when dragging
Cupertino Slider:
Value- Current valueMin- Minimum valueMax- Maximum valueDivisions- Number of discrete divisions (optional)OnChanged- Called when value changesActiveColor- Color of active track
volume := signals.New(50.0)
&widgets.Column{
Children: []goflow.Widget{
&widgets.Row{
Children: []goflow.Widget{
widgets.NewIcon(widgets.IconVolumeDown),
&widgets.Expanded{
Child: material.NewSlider(volume.Get(), 0, 100, func(v float64) {
volume.Set(v)
setVolume(v)
}),
},
widgets.NewIcon(widgets.IconVolumeUp),
},
},
&widgets.Text{
Data: fmt.Sprintf("Volume: %.0f%%", volume.Get()),
},
},
}rating := signals.New(3.0)
divisions := 5
slider := material.NewSlider(rating.Get(), 0, 5, func(v float64) {
rating.Set(v)
})
slider.Divisions = &divisions
slider.Label = fmt.Sprintf("%.0f stars", rating.Get())type FormData struct {
Name *signals.Signal[string]
Email *signals.Signal[string]
Age *signals.Signal[int]
Agree *signals.Signal[bool]
}
func NewFormData() *FormData {
return &FormData{
Name: signals.New(""),
Email: signals.New(""),
Age: signals.New(0),
Agree: signals.New(false),
}
}formValid := signals.NewComputed(func() bool {
return form.Name.Get() != "" &&
form.Email.Get() != "" &&
form.Agree.Get()
})
submitButton.Enabled = formValid.Get()emailError := signals.NewComputed(func() string {
email := form.Email.Get()
if email == "" {
return ""
}
if !isValidEmail(email) {
return "Invalid email address"
}
return ""
})
textField.Decoration.ErrorText = emailError.Get()&widgets.Column{
Children: []goflow.Widget{
buildTextField(form.Name, "Name"),
buildTextField(form.Email, "Email"),
material.NewButton(
&widgets.Text{Data: "Submit"},
func() {
if !formValid.Get() {
showSnackbar("Please fill all required fields")
return
}
submitForm()
},
),
},
}isLoading := signals.New(false)
submitButton := material.NewButton(
func() goflow.Widget {
if isLoading.Get() {
return &widgets.Text{Data: "Submitting..."}
}
return &widgets.Text{Data: "Submit"}
}(),
func() {
if isLoading.Get() {
return
}
isLoading.Set(true)
go func() {
err := submitForm()
isLoading.Set(false)
if err != nil {
showError(err)
}
}()
},
)
submitButton.Enabled = !isLoading.Get()