-
Notifications
You must be signed in to change notification settings - Fork 9
Guide Recipes Settings Form
MeowLynxSea edited this page Jan 24, 2026
·
3 revisions
This recipe shows how to build a real settings form using Yororen UI primitives.
Goal:
- Consistent label/control alignment
- Inline help + inline error
- “Uncontrolled” text input (smooth typing)
- A file path picker with status coloring
-
Formprimitives:form(),form_row(...),help_text(...),inline_error(...) - Inputs:
TextInput,Switch,FilePathInput,NumberInput - Theme-driven validation:
ValidationState
use std::path::PathBuf;
use gpui::{ElementId, SharedString, Window, div, px};
use yororen_ui::component::{
FilePathStatus, ValidationState,
button, file_path_input, form, form_row, help_text, inline_error,
label, number_input, switch, text_input,
};
fn render_settings(window: &mut Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
// Own your settings state in keyed state.
let username = window.use_keyed_state("settings:username", cx, |_, _| SharedString::new_static(""));
let notifications = window.use_keyed_state("settings:notifications", cx, |_, _| true);
let retries = window.use_keyed_state("settings:retries", cx, |_, _| 3.0f64);
let workspace_path = window.use_keyed_state("settings:workspace", cx, |_, _| None::<PathBuf>);
// Derive validation.
let username_value = username.read(cx).as_ref().trim();
let username_len = username_value.chars().count();
let username_validation = if username_len == 0 {
ValidationState::Error
} else if username_len < 3 {
ValidationState::Warning
} else {
ValidationState::Success
};
let username_error = (username_len == 0).then_some("Username cannot be empty");
let path_validation = if workspace_path.read(cx).is_some() {
ValidationState::Success
} else {
ValidationState::Warning
};
let path_status = workspace_path.read(cx).as_ref().map(|_| match path_validation {
ValidationState::Success => FilePathStatus::Ok,
ValidationState::Warning => FilePathStatus::Warning,
ValidationState::Error => FilePathStatus::Error,
});
form()
.child(
form_row(
"Username",
div().w(px(320.)).child(
text_input()
.key(ElementId::from((ElementId::from("settings"), "username")))
.placeholder("Enter a username…")
.on_change({
let username = username.clone();
move |value, _window, cx| {
username.update(cx, |state, _| *state = value);
}
}),
),
)
.validation(username_validation)
.help(help_text("At least 3 characters."))
.error(username_error.map(inline_error)),
)
.child(
form_row(
"Notifications",
switch()
.key(("settings", "notifications"))
.checked(*notifications.read(cx))
.on_toggle({
let notifications = notifications.clone();
move |checked, _ev, _window, cx| {
notifications.update(cx, |state, _| *state = checked);
}
}),
)
.validation(ValidationState::Success),
)
.child(
form_row(
"Retries",
div().w(px(240.)).child(
number_input()
.key(("settings", "retries"))
.min(0.0)
.max(10.0)
.step(1.0)
.value(*retries.read(cx))
.on_change({
let retries = retries.clone();
move |value, _window, cx| {
retries.update(cx, |state, _| *state = value);
}
}),
),
)
.validation(ValidationState::Success),
)
.child(
form_row(
"Workspace",
div().w(px(520.)).child({
let mut input = file_path_input()
.key(ElementId::from((ElementId::from("settings"), "workspace")))
.placeholder("Choose a folder…")
.on_change({
let workspace_path = workspace_path.clone();
move |path, _window, cx| {
workspace_path.update(cx, |state, _| *state = Some(path));
}
});
if let Some(status) = path_status {
input = input.status(status);
}
input
}),
)
.validation(path_validation)
.help(help_text("Used for storing caches and profiles.")),
)
.child(
div()
.pt_3()
.flex()
.justify_end()
.child(button().child(label("Save").inherit_color(true))),
)
}-
TextInputis best used "uncontrolled": avoid feeding.content(...)every render. -
FilePathInputowns internal value state; useon_changeto persist it.
Yororen UI v0.3.0 · repository · Apache-2.0 · This wiki documents Yororen UI v0.3.0.
This wiki documents Yororen UI v0.3.0 — the headless-core, swappable-renderer build.