Form
Form lays out typed fields and an optional footer. The application owns values, validation, submission, and responsive column choices.
#Import
use gpui_kit::component::form::{field, v_form, h_form, Form, Field};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Form, FormGrid, FormSection, FormSeparator, FormField, FormRow } from "form.slint";use iced::widget::text_editor::{Action, Content};
use iced::widget::{column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Element, Length, Task, Theme};#Predictable composition
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// One form built the way the page builds it: the label beside the control in
// the column the form measures, two field columns, and the commit row in
// `footer`.
let composed = Form::new()
.label_layout(LabelLayout::Beside)
.columns(2)
.gap(12.0)
.child(
Field::new().label("Name").child(
Input::new("pc-name")
.value(app.name.clone())
.placeholder("Ada Lovelace")
.on_input(Message::Name),
),
)
.child(
Field::new().label("Email").required(true).child(
Input::new("pc-email")
.value(app.email.clone())
.placeholder("ada@example.com")
.on_input(Message::Email),
),
)
.footer(
row![
Button::new("Cancel")
.outline()
.on_press(Message::ComposeCancel),
Button::new("Save")
.variant(button::Variant::Primary)
.on_press(Message::ComposeSave),
]
.spacing(8.0),
);Form::new() defaults to one column with labels above controls. label_layout(Axis::Horizontal) places labels beside controls; columns(2) independently creates two field columns. Existing horizontal(), vertical(), layout(Axis), h_form(), and v_form() remain available.
Form::new()
.label_layout(Axis::Horizontal)
.columns(2)
.child(Field::new().label("Name").child(Input::new(&name_input)))
.child(Field::new().label("Email").child(Input::new(&email_input)))
.footer(Button::new("save").label("Save"))SlintNo Slint version of this example yet.
child accepts a Field. Put commands in footer, which spans all columns and aligns its content to the trailing edge. Attach submission behavior to the supplied Button; Form does not submit automatically. See the complete application recipe for retained state, callbacks, and window setup.
#Usage
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The basic form: one column, labels above their controls.
let basic = Form::vertical()
.child(
Field::new().label("Name").child(
Input::new("use-name")
.value(app.name.clone())
.on_input(Message::Name),
),
)
.child(
Field::new().label("Email").required(true).child(
Input::new("use-email")
.value(app.email.clone())
.on_input(Message::Email),
),
)
.child(
Field::new().label("Bio").child(
Textarea::new(&app.bio)
.placeholder("Tell us about yourself")
.id("use-bio")
.on_action(Message::Bio),
),
);
// The horizontal form: the same fields with the labels beside them.
let horizontal = Form::horizontal()
.child(
Field::new().label("First Name").child(
Input::new("h-first")
.value(app.h_first.clone())
.placeholder("Ada")
.on_input(Message::HFirst),
),
)
.child(
Field::new().label("Last Name").child(
Input::new("h-last")
.value(app.h_last.clone())
.placeholder("Lovelace")
.on_input(Message::HLast),
),
);
// The multi-column form: two field columns, and the Bio field spanning both.
let multi = Form::vertical()
.columns(2)
.child(
Field::new().label("First Name").child(
Input::new("m-first")
.value(app.h_first.clone())
.placeholder("Ada")
.on_input(Message::HFirst),
),
)
.child(
Field::new().label("Last Name").child(
Input::new("m-last")
.value(app.h_last.clone())
.placeholder("Lovelace")
.on_input(Message::HLast),
),
)
.child(
Field::new().label("Bio").col_span(2).child(
Textarea::new(&app.bio)
.rows(3)
.placeholder("Tell us about yourself")
.id("m-bio")
.on_action(Message::Bio),
),
);
// The registration form: the one example that runs validation end to end.
// **Create account** checks the email and the password and writes the strings
// their fields show; typing a valid value clears the one it corrects.
let registration = Form::vertical()
.child(
Field::new().label("Personal Information").child(
row![
Input::new("reg-first")
.value(app.reg_first.clone())
.placeholder("First name")
.on_input(Message::RegFirst),
Input::new("reg-last")
.value(app.reg_last.clone())
.placeholder("Last name")
.on_input(Message::RegLast),
]
.spacing(8.0),
),
)
.child(
Field::new()
.label("Email")
.required(true)
.error(app.reg_error_email.clone())
.child(
Input::new("reg-email")
.value(app.reg_email.clone())
.invalid(!app.reg_error_email.is_empty())
.placeholder("you@example.com")
.on_input(Message::RegEmail),
),
)
.child(
Field::new()
.label("Password")
.required(true)
.description("Must be at least 8 characters")
.error(app.reg_error_password.clone())
.child(
Input::new("reg-password")
.value(app.reg_password.clone())
.secure(true)
.invalid(!app.reg_error_password.is_empty())
.placeholder("Password")
.on_input(Message::RegPassword),
),
)
.child(
Field::new().label("Confirm Password").required(true).child(
Input::new("reg-confirm")
.value(app.reg_confirm.clone())
.secure(true)
.placeholder("Confirm password")
.on_input(Message::RegConfirm),
),
)
.child(
Field::new()
.label_indent(false)
.error(app.reg_error_terms.clone())
.child(
Checkbox::new(app.reg_terms)
.label("I agree to the Terms of Service")
.on_toggle(Message::RegTerms),
),
)
.child(
Field::new().label_indent(false).child(
Button::new("Create account")
.variant(button::Variant::Primary)
.on_press(Message::RegCreate),
),
);
// The settings form: `Field::section` is the heading over a group and
// `Field::separator` the rule under it, which is Slint's `FormSection` and
// `FormSeparator`.
let settings = Form::vertical()
.child(Field::section("Profile").description("How you appear to others."))
.child(Field::separator())
.child(
Field::new().label("Display Name").child(
Input::new("set-display")
.value(app.name.clone())
.placeholder("Ada Lovelace")
.on_input(Message::Name),
),
)
.child(
Field::new().label("Email").child(
Input::new("set-email")
.value(app.email.clone())
.placeholder("ada@example.com")
.on_input(Message::Email),
),
)
.child(Field::section("Preferences").description("Choose how the app behaves."))
.child(Field::separator())
.child(
Field::new().label("Notifications").child(
Switch::new(app.g_notify)
.label("Enable notifications")
.on_toggle(Message::GNotify),
),
);
let usage = column![
basic,
Field::separator(),
horizontal,
Field::separator(),
multi,
Field::separator(),
registration,
muted_line(app.reg_status.as_str()),
settings,
]
.spacing(16.0);#Basic Form
v_form()
.child(
field()
.label("Name")
.child(Input::new(&name_input))
)
.child(
field()
.label("Email")
.child(Input::new(&email_input))
.required(true)
)Form {
FormField {
label: "Name";
Input {
text <=> root.name;
placeholder: "Ada Lovelace";
}
}
FormField {
label: "Email";
required: true;
Input {
text <=> root.email;
placeholder: "ada@example.com";
}
}
HorizontalLayout {
alignment: end;
Button {
text: "Save";
variant: ButtonVariant.primary;
}
}
}#Horizontal Form Layout
h_form()
.label_width(px(120.))
.child(
field()
.label("First Name")
.child(Input::new(&first_name))
)
.child(
field()
.label("Last Name")
.child(Input::new(&last_name))
)Form {
FormRow {
label: "First Name";
label-width: 120px;
Input {
text <=> root.first;
placeholder: "Ada";
}
}
FormRow {
label: "Last Name";
label-width: 120px;
Input {
text <=> root.last;
placeholder: "Lovelace";
}
}
}#Multi-Column Form
v_form()
.columns(2) // Two-column layout
.child(
field()
.label("First Name")
.child(Input::new(&first_name))
)
.child(
field()
.label("Last Name")
.child(Input::new(&last_name))
)
.child(
field()
.label("Bio")
.col_span(2) // Span across both columns
.child(Input::new(&bio_input))
)FormGrid {
FormField {
col: 0;
label: "First Name";
Input {
text <=> root.m-first;
placeholder: "Ada";
}
}
FormField {
col: 1;
label: "Last Name";
Input {
text <=> root.m-last;
placeholder: "Lovelace";
}
}
FormField {
col: 0;
colspan: 2;
label: "Bio";
Textarea {
text <=> root.m-bio;
placeholder: "Tell us about yourself";
}
}
}#User Registration Form
struct RegistrationForm {
first_name: Entity<InputState>,
last_name: Entity<InputState>,
email: Entity<InputState>,
password: Entity<InputState>,
confirm_password: Entity<InputState>,
terms_accepted: bool,
}
impl Render for RegistrationForm {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_form()
.large()
.child(
field()
.label("Personal Information")
.label_indent(false)
.child(
h_flex()
.gap_3()
.child(
div().flex_1().child(
Input::new(&self.first_name)
.placeholder("First name")
)
)
.child(
div().flex_1().child(
Input::new(&self.last_name)
.placeholder("Last name")
)
)
)
)
.child(
field()
.label("Email")
.required(true)
.child(Input::new(&self.email))
)
.child(
field()
.label("Password")
.required(true)
.description("Must be at least 8 characters")
.child(Input::new(&self.password))
)
.child(
field()
.label("Confirm Password")
.required(true)
.child(Input::new(&self.confirm_password))
)
.child(
field()
.label_indent(false)
.child(
Checkbox::new("terms")
.label("I agree to the Terms of Service")
.checked(self.terms_accepted)
.on_click(cx.listener(|this, checked, _, cx| {
this.terms_accepted = *checked;
cx.notify();
}))
)
)
.child(
field()
.label_indent(false)
.child(
Button::new("register")
.primary()
.large()
.w_full()
.child("Create Account")
)
)
}
}Form {
FormField {
label: "Personal Information";
HorizontalLayout {
spacing: 12px;
cross-axis-alignment: start;
Input {
text <=> root.r-first;
placeholder: "First name";
}
Input {
text <=> root.r-last;
placeholder: "Last name";
}
}
}
FormField {
label: "Email";
required: true;
Input {
text <=> root.r-email;
placeholder: "you@example.com";
}
}
FormField {
label: "Password";
required: true;
description: "Must be at least 8 characters";
Input {
text <=> root.r-password;
masked: true;
mask-toggle: true;
placeholder: "Password";
}
}
FormField {
label: "Confirm Password";
required: true;
Input {
text <=> root.r-confirm;
masked: true;
mask-toggle: true;
placeholder: "Confirm password";
}
}
Checkbox {
checked <=> root.r-terms;
label: "I agree to the Terms of Service";
}
Button {
text: "Create Account";
variant: ButtonVariant.primary;
horizontal-stretch: 1;
}
}#Settings Form with Sections
v_form()
.column(2)
.child(
field()
.label("Profile")
.label_indent(false)
.col_span(2)
.child(Separator::horizontal())
)
.child(
field()
.label("Display Name")
.child(Input::new(&display_name))
)
.child(
field()
.label("Email")
.child(Input::new(&email))
)
.child(
field()
.label("Bio")
.col_span(2)
.items_start()
.child(Input::new(&bio))
)
.child(
field()
.label("Preferences")
.label_indent(false)
.col_span(2)
.child(Separator::horizontal())
)
.child(
field()
.label("Theme")
.child(Select::new(&theme_state))
)
.child(
field()
.label("Language")
.child(Select::new(&language_state))
)
.child(
field()
.label_indent(false)
.child(Switch::new("notifications").label("Enable notifications"))
)
.child(
field()
.label_indent(false)
.child(Switch::new("marketing").label("Marketing emails"))
)Form {
FormSection {
title: "Profile";
description: "How you appear to others.";
}
FormSeparator {}
FormRow {
label: "Display Name";
label-width: 160px;
Input {
text <=> root.s-display;
placeholder: "Ada Lovelace";
}
}
FormRow {
label: "Email";
label-width: 160px;
Input {
text <=> root.s-email;
placeholder: "ada@example.com";
}
}
FormField {
label: "Bio";
Textarea {
text <=> root.s-bio;
placeholder: "A short bio";
}
}
FormSection {
title: "Preferences";
description: "Choose how the app behaves.";
}
FormSeparator {}
FormRow {
label: "Theme";
label-width: 160px;
Select {
items: root.theme-items;
selected <=> root.s-theme;
}
}
FormRow {
label: "Language";
label-width: 160px;
Select {
items: root.language-items;
selected <=> root.s-language;
}
}
Switch {
checked <=> root.s-notify;
label: "Enable notifications";
}
Switch {
checked <=> root.s-marketing;
label: "Marketing emails";
}
}#Contact Form
v_form()
.child(
field()
.label("Contact Information")
.child(
h_flex()
.gap_2()
.child(
Select::new(&prefix_state)
.w(px(80.))
)
.child(
div().flex_1().child(
Input::new(&name_input)
.placeholder("Your name")
)
)
)
)
.child(
field()
.label("Email")
.required(true)
.child(Input::new(&email_input))
)
.child(
field()
.label("Subject")
.child(Select::new(&subject_state))
)
.child(
field()
.label("Message")
.required(true)
.items_start()
.description("Please describe your inquiry in detail")
.child(Input::new(&message_input))
)
.child(
field()
.label_indent(false)
.child(
h_flex()
.gap_2()
.justify_between()
.child(
Checkbox::new("copy")
.label("Send me a copy")
)
.child(
h_flex()
.gap_2()
.child(Button::new("cancel").child("Cancel"))
.child(Button::new("send").primary().child("Send Message"))
)
)
)Form {
FormField {
label: "Contact Information";
HorizontalLayout {
spacing: 8px;
cross-axis-alignment: center;
Select {
preferred-width: 90px;
horizontal-stretch: 0;
items: root.prefix-items;
selected <=> root.c-prefix;
}
Input {
text <=> root.c-name;
placeholder: "Your name";
}
}
}
FormField {
label: "Email";
required: true;
Input {
text <=> root.c-email;
placeholder: "you@example.com";
}
}
FormField {
label: "Subject";
Select {
items: root.subject-items;
selected <=> root.c-subject;
}
}
FormField {
label: "Message";
required: true;
description: "Please describe your inquiry in detail";
Textarea {
text <=> root.c-message;
placeholder: "How can we help?";
}
}
HorizontalLayout {
alignment: space-between;
cross-axis-alignment: center;
Checkbox {
checked <=> root.c-copy;
label: "Send me a copy";
}
HorizontalLayout {
spacing: 8px;
Button {
text: "Cancel";
}
Button {
text: "Send Message";
variant: ButtonVariant.primary;
}
}
}
}#Form Container and Layout
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The vertical default, the horizontal label layout, and the size ladder.
let vertical = Form::vertical()
.gap(12.0)
.child(
Field::new().label("Name").child(
Input::new("vl-name")
.value(app.name.clone())
.on_input(Message::Name),
),
)
.child(
Field::new().label("Email").child(
Input::new("vl-email")
.value(app.email.clone())
.on_input(Message::Email),
),
);
let layout_horizontal = Form::horizontal()
.child(
Field::new().label("Name").child(
Input::new("lh-name")
.value(app.name.clone())
.on_input(Message::Name),
),
)
.child(
Field::new().label("Email").child(
Input::new("lh-email")
.value(app.email.clone())
.on_input(Message::Email),
),
);
let large = Form::vertical().size(ControlSize::Large).child(
Field::new().label("Title").child(
Input::new("sz-large")
.size(ControlSize::Large)
.value(app.sizing_title.clone())
.placeholder("Large field")
.on_input(Message::SizingTitle),
),
);
let small = Form::vertical().size(ControlSize::Small).child(
Field::new().label("Code").child(
Input::new("sz-small")
.size(ControlSize::Small)
.value(app.sizing_code.clone())
.placeholder("Small field")
.on_input(Message::SizingCode),
),
);
let container_layout = column![vertical, layout_horizontal, large, small].spacing(20.0);#Vertical Layout (Default)
v_form()
.gap(px(12.))
.child(field().label("Name").child(input))
.child(field().label("Email").child(email_input))SlintNo Slint version of this example yet.
#Horizontal Layout
h_form()
.label_width(px(100.))
.child(field().label("Name").child(input))
.child(field().label("Email").child(email_input))SlintNo Slint version of this example yet.
#Custom Sizing
v_form()
.large() // Large form size
.label_text_size(rems(1.2))
.child(field().label("Title").child(input))
v_form()
.small() // Small form size
.child(field().label("Code").child(input))Form {
FormField {
label: "Title";
size: ControlSize.large;
Input {
size: ControlSize.large;
text <=> root.big-title;
placeholder: "Large field";
}
}
FormField {
label: "Code";
size: ControlSize.small;
Input {
size: ControlSize.small;
text <=> root.small-code;
placeholder: "Small field";
}
}
}#Form Validation
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// `required` draws the asterisk; `error` places the message, and the
// description steps aside while one is set. Both are the application's: the
// Validate button writes the string the field shows.
let required = Form::vertical()
.child(
Field::new()
.label("Email")
.required(true)
.error(app.v_error.clone())
.child(
Input::new("val-email")
.value(app.v_email.clone())
.invalid(!app.v_error.is_empty())
.placeholder("you@example.com")
.on_input(Message::VEmail),
),
)
.child(
Field::new()
.label("Password")
.required(true)
.description("Must be at least 8 characters long")
.child(
Input::new("val-password")
.secure(true)
.placeholder("Password"),
),
)
.footer(
Button::new("Validate")
.variant(button::Variant::Primary)
.on_press(Message::VValidate),
);
let dynamic = Form::vertical().child(
Field::new()
.label("Bio")
.description(if app.bio.text().chars().count() > 40 {
"That is plenty — shorter reads better."
} else {
"Use at most 100 words to describe yourself."
})
.child(
Textarea::new(&app.bio)
.rows(3)
.id("val-bio")
.on_action(Message::Bio),
),
);
let conditional = Form::vertical()
.child(
Field::new().label("Account Type").child(
Select::new([Item::new("Personal"), Item::new("Business")])
.selected(app.v_account)
.open(app.select_open)
.width(Length::Fill)
.on_open_change(Message::SelectOpen)
.on_chosen(Message::VAccount),
),
)
.child(
Field::new()
.label("Company Name")
.visible(app.v_account == Some(1))
.child(
Input::new("val-company")
.value(app.v_company.clone())
.placeholder("Acme Inc.")
.on_input(Message::VCompany),
),
)
.child(
Field::new()
.label("Tax ID")
.required(true)
.visible(app.v_account == Some(1))
.child(
Input::new("val-tax")
.value(app.v_tax_id.clone())
.placeholder("US-0000000")
.on_input(Message::VTaxId),
),
);
let validation = column![required, dynamic, conditional].spacing(20.0);#Required Fields
field()
.label("Email")
.required(true) // Shows asterisk (*) next to label
.child(Input::new(&email_input))Form {
FormField {
label: "Email";
required: true;
error: "Enter a valid email address.";
Input {
text <=> root.required-email;
invalid: true;
placeholder: "you@example.com";
}
}
FormField {
label: "Password";
required: true;
description: "Must be at least 8 characters long";
Input {
masked: true;
mask-toggle: true;
placeholder: "Password";
}
}
}#Field Descriptions
field()
.label("Password")
.description("Must be at least 8 characters long")
.child(Input::new(&password_input))SlintNo Slint version of this example yet.
#Dynamic Descriptions
field()
.label("Bio")
.description_fn(|_, _| {
div().child("Use at most 100 words to describe yourself.")
})
.child(Input::new(&bio_input))SlintNo Slint version of this example yet.
#Field Visibility
field()
.label("Admin Settings")
.visible(user.is_admin()) // Conditionally show field
.child(Switch::new("admin-mode"))SlintNo Slint version of this example yet.
#Submit Handling
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// The form does not submit; the button reports, the application validates and
// the status line under the form is what changed.
let submit = Form::vertical()
.child(
Field::new().label("Name").child(
Input::new("sub-name")
.value(app.s_name.clone())
.on_input(Message::SName),
),
)
.child(
Field::new().label("Email").child(
Input::new("sub-email")
.value(app.s_email.clone())
.on_input(Message::SEmail),
),
)
.child(
Field::new().label_indent(false).child(
Button::new("Submit")
.variant(button::Variant::Primary)
.on_press(Message::SSubmit),
),
);
let actions = Form::vertical()
.child(
Field::new()
.label("Title")
.child(Input::new("act-title").placeholder("Release notes")),
)
.child(
Field::new()
.label("Content")
.child(Input::new("act-content").placeholder("What changed?")),
)
.footer(
row![
Button::new("Save").variant(button::Variant::Primary),
Button::new("Cancel").outline(),
Button::new("Preview").variant(button::Variant::Ghost),
]
.spacing(8.0),
);
let submission = column![submit, muted_line(app.s_status.as_str()), actions].spacing(20.0);#Basic Submit Pattern
struct FormView {
name_input: Entity<InputState>,
email_input: Entity<InputState>,
}
impl FormView {
fn submit(&mut self, cx: &mut Context<Self>) {
let name = self.name_input.read(cx).value();
let email = self.email_input.read(cx).value();
// Validate inputs
if name.is_empty() || email.is_empty() {
// Show validation error
return;
}
// Submit form data
self.handle_submit(name, email, cx);
}
}
// Form with submit button
v_form()
.child(field().label("Name").child(Input::new(&self.name_input)))
.child(field().label("Email").child(Input::new(&self.email_input)))
.child(
field()
.label_indent(false)
.child(
Button::new("submit")
.primary()
.child("Submit")
.on_click(cx.listener(|this, _, _, cx| this.submit(cx)))
)
)SlintNo Slint version of this example yet.
#Form with Action Buttons
v_form()
.child(field().label("Title").child(Input::new(&title)))
.child(field().label("Content").child(Input::new(&content)))
.child(
field()
.label_indent(false)
.child(
h_flex()
.gap_2()
.child(Button::new("save").primary().child("Save"))
.child(Button::new("cancel").child("Cancel"))
.child(Button::new("preview").outline().child("Preview"))
)
)SlintNo Slint version of this example yet.
#Field Groups
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// One label over a row of two controls; one label over a stack; and the
// controls that are not text fields at all.
let related = Form::vertical()
.child(
Field::new().label("Name").child(
row![
Input::new("grp-first")
.value(app.g_first.clone())
.placeholder("First name")
.on_input(Message::GFirst),
Input::new("grp-last")
.value(app.g_last.clone())
.placeholder("Last name")
.on_input(Message::GLast),
]
.spacing(8.0),
),
)
.child(
Field::new().label("Address").child(
column![
Input::new("grp-street")
.value(app.g_street.clone())
.placeholder("Street address")
.on_input(Message::GStreet),
row![
Input::new("grp-city")
.value(app.g_city.clone())
.placeholder("City")
.on_input(Message::GCity),
Input::new("grp-zip")
.value(app.g_zip.clone())
.placeholder("ZIP")
.width(120.0)
.on_input(Message::GZip),
]
.spacing(8.0),
]
.spacing(8.0),
),
);
let custom = Form::vertical()
.child(
Field::new().label("Notifications").child(
Switch::new(app.g_notify)
.label("Email notifications")
.on_toggle(Message::GNotify),
),
)
.child(
Field::new().label("Volume").child(
Slider::new(app.g_volume)
.minimum(0.0)
.maximum(100.0)
.on_change(Message::GVolume),
),
)
.child(
Field::new().label("Quantity").child(
NumberInput::new("grp-qty")
.value(app.g_quantity)
.minimum(1.0)
.maximum(20.0)
.on_change(Message::GQuantity),
),
);
let groups = column![related, custom].spacing(20.0);#Related Fields
v_form()
.child(
field()
.label("Name")
.child(
h_flex()
.gap_2()
.child(div().flex_1().child(Input::new(&first_name)))
.child(div().flex_1().child(Input::new(&last_name)))
)
)
.child(
field()
.label("Address")
.items_start() // Align to start for multi-line content
.child(
v_flex()
.gap_2()
.child(Input::new(&street))
.child(
h_flex()
.gap_2()
.child(div().flex_1().child(Input::new(&city)))
.child(div().w(px(100.)).child(Input::new(&zip)))
)
)
)Form {
FormField {
label: "Name";
HorizontalLayout {
spacing: 12px;
cross-axis-alignment: start;
Input {
text <=> root.first;
placeholder: "First name";
}
Input {
text <=> root.last;
placeholder: "Last name";
}
}
}
FormField {
label: "Address";
VerticalLayout {
spacing: 8px;
Input {
text <=> root.addr-street;
placeholder: "Street address";
}
HorizontalLayout {
spacing: 12px;
cross-axis-alignment: start;
Input {
text <=> root.addr-city;
placeholder: "City";
}
Input {
preferred-width: 120px;
horizontal-stretch: 0;
text <=> root.addr-zip;
placeholder: "ZIP";
}
}
}
}
}#Custom Field Components
field()
.label("Theme Color")
.child(ColorPicker::new(&color_state).small())
field()
.label("Birth Date")
.description("We'll send you a birthday gift!")
.child(DatePicker::new(&date_state))
field()
.label("Notifications")
.child(
v_flex()
.gap_2()
.child(Switch::new("email").label("Email notifications"))
.child(Switch::new("push").label("Push notifications"))
.child(Switch::new("sms").label("SMS notifications"))
)SlintNo Slint version of this example yet.
#Conditional Fields
v_form()
.child(
field()
.label("Account Type")
.child(Select::new(&account_type))
)
.child(
field()
.label("Company Name")
.visible(is_business_account) // Show only for business accounts
.child(Input::new(&company_name))
)
.child(
field()
.label("Tax ID")
.visible(is_business_account)
.required(is_business_account)
.child(Input::new(&tax_id))
)Form {
FormField {
label: "Account Type";
Select {
items: root.account-items;
selected <=> root.account-type;
}
}
if root.is-business: FormField {
label: "Company Name";
Input {
text <=> root.company;
placeholder: "Acme Inc.";
}
}
if root.is-business: FormField {
label: "Tax ID";
required: true;
Input {
text <=> root.tax-id;
placeholder: "US-0000000";
}
}
}#Grid Layout and Positioning
GPUINo GPUI version of this example yet.
SlintNo Slint version of this example yet.
// Three columns, a field spanning all three, and a field placed at a column
// of its own.
let spanning = Form::vertical()
.columns(3)
.child(
Field::new().label("First").child(
Input::new("g-a")
.value(app.grid_a.clone())
.placeholder("One")
.on_input(Message::GridA),
),
)
.child(
Field::new().label("Second").child(
Input::new("g-b")
.value(app.grid_b.clone())
.placeholder("Two")
.on_input(Message::GridB),
),
)
.child(
Field::new().label("Third").child(
Input::new("g-c")
.value(app.grid_c.clone())
.placeholder("Three")
.on_input(Message::GridC),
),
)
.child(
Field::new().label("Full Width").col_span(3).child(
Input::new("g-full")
.value(app.grid_full.clone())
.placeholder("Spans all three columns")
.on_input(Message::GridFull),
),
);
let positioning = Form::vertical()
.columns(4)
.child(
Field::new()
.label("A")
.child(Input::new("g-p1").placeholder("First")),
)
.child(
Field::new()
.label("B")
.child(Input::new("g-p2").placeholder("Second")),
)
.child(
Field::new()
.label("Positioned")
.col_start(1)
.col_span(2)
.child(
Input::new("g-pos")
.value(app.grid_positioned.clone())
.placeholder("Columns two and three")
.on_input(Message::GridPositioned),
),
);
// The page's Responsive Layout sample: the application chooses the column
// count from the window, and the component clamps the Bio field's span to
// whatever the count is, exactly as the page's `.when(!is_mobile, …)` does.
let responsive = Form::vertical()
.columns(if app.narrow { 1 } else { 2 })
.child(
Field::new().label("Name").child(
Input::new("resp-name")
.value(app.resp_name.clone())
.placeholder("Ada Lovelace")
.on_input(Message::RespName),
),
)
.child(
Field::new().label("Email").child(
Input::new("resp-email")
.value(app.resp_email.clone())
.placeholder("ada@example.com")
.on_input(Message::RespEmail),
),
)
.child(
Field::new().label("Bio").col_span(2).child(
Textarea::new(&app.bio)
.rows(3)
.placeholder("Tell us about yourself")
.id("resp-bio")
.on_action(Message::Bio),
),
);
let grid = column![spanning, positioning, responsive].spacing(20.0);#Column Spanning
v_form()
.columns(3) // Three-column grid
.child(field().label("First").child(input1))
.child(field().label("Second").child(input2))
.child(field().label("Third").child(input3))
.child(
field()
.label("Full Width")
.col_span(3) // Spans all three columns
.child(Input::new(&full_width))
)FormGrid {
FormField {
col: 0;
label: "First";
Input {
text <=> root.col-a;
placeholder: "One";
}
}
FormField {
col: 1;
label: "Second";
Input {
text <=> root.col-b;
placeholder: "Two";
}
}
FormField {
col: 2;
label: "Third";
Input {
text <=> root.col-c;
placeholder: "Three";
}
}
FormField {
col: 0;
colspan: 3;
label: "Full Width";
Input {
text <=> root.col-full;
placeholder: "Spans all three columns";
}
}
}#Column Positioning
v_form()
.columns(4)
.child(field().label("A").child(input_a))
.child(field().label("B").child(input_b))
.child(
field()
.label("Positioned")
.col_start(1) // Start at column 1
.col_span(2) // Span 2 columns
.child(input_positioned)
)SlintNo Slint version of this example yet.
#Responsive Layout
v_form()
.columns(if is_mobile { 1 } else { 2 })
.child(field().label("Name").child(name_input))
.child(field().label("Email").child(email_input))
.child(
field()
.label("Bio")
.when(!is_mobile, |field| field.col_span(2))
.child(bio_input)
)SlintNo Slint version of this example yet.