| Crates.io | waterui-form |
| lib.rs | waterui-form |
| version | 0.2.1 |
| created_at | 2025-12-14 08:04:45.079027+00 |
| updated_at | 2025-12-14 11:29:55.57779+00 |
| description | Form components for WaterUI (inputs, buttons, validation) |
| homepage | |
| repository | https://github.com/water-rs/waterui |
| max_upload_size | |
| id | 1983993 |
| size | 77,338 |
A comprehensive form building system for WaterUI applications with automatic component generation, validation, and secure data handling.
waterui-form provides an ergonomic, type-safe approach to building interactive forms in WaterUI applications. The crate centers around the FormBuilder trait, which enables automatic mapping from Rust data structures to platform-native UI components. It includes specialized pickers (color, date, multi-date), secure input handling with automatic memory zeroing, and a flexible validation system.
Key features:
#[derive(FormBuilder)] macrouser_name becomes "User Name"This crate is part of the WaterUI workspace and integrates tightly with waterui-core for reactivity, waterui-controls for base components, and waterui-layout for composition.
Add to your Cargo.toml:
[dependencies]
waterui-form = "0.1.0"
# Optional: enable serde support
waterui-form = { version = "0.1.0", features = ["serde"] }
The most common use case is deriving FormBuilder on a struct:
use waterui::prelude::*;
use waterui_form::{FormBuilder, form};
#[derive(Default, Clone, Debug, FormBuilder)]
struct UserProfile {
/// Enter your full name
name: String,
/// Your current age
age: i32,
/// Account is active
active: bool,
}
fn profile_form() -> impl View {
let form_binding = UserProfile::binding();
vstack((
form(&form_binding),
button("Save", move || {
tracing::debug!("Profile: {:?}", form_binding.get());
}),
))
}
This generates a vertical stack with:
The FormBuilder trait is the foundation of the form system. It maps Rust types to UI components:
| Rust Type | UI Component | Description |
|---|---|---|
String, Str |
TextField |
Single-line text input with optional placeholder |
bool |
Toggle |
Boolean switch/checkbox |
i32 |
Stepper |
Integer stepper with +/- buttons |
f32, f64 |
Slider |
Numeric slider (0.0-1.0 range by default) |
Color |
ColorPicker |
Platform-native color selection |
Date |
DatePicker |
Date/time selection (requires time crate types) |
BTreeSet<Date> |
MultiDatePicker |
Multiple date selection |
Secure |
SecureField |
Masked password input with memory zeroing |
The derive macro automatically implements FormBuilder by:
user_name → "User Name"/// Enter email becomes placeholder textThe macro requires the struct to have named fields and generates an implementation compatible with the Project trait for field-level reactive bindings.
Forms use Binding<T> from the nami crate for reactive state. The Project trait (auto-derived alongside FormBuilder) allows accessing individual field bindings:
let form_binding = UserProfile::binding();
let projected = form_binding.project();
// Access individual field bindings
projected.name.set("Alice".to_string());
projected.age.set(30);
// Read entire form state
let profile = form_binding.get();
Initialize a form with existing data instead of defaults:
use waterui::prelude::*;
use waterui_form::{FormBuilder, form};
#[derive(Default, Clone, Debug, FormBuilder)]
struct LoginForm {
username: String,
password: String,
}
fn login_view() -> impl View {
let initial = LoginForm {
username: "alice@example.com".to_string(),
password: String::new(),
};
let form_binding = Binding::new(initial);
vstack((
form(&form_binding),
button("Login", move || {
let credentials = form_binding.get();
tracing::debug!("Logging in as: {}", credentials.username);
}),
))
}
Use projected bindings to display live form data:
use waterui::prelude::*;
use waterui_form::{FormBuilder, form};
#[derive(Default, Clone, Debug, FormBuilder)]
struct ContactForm {
/// Your full name
name: String,
/// Email address
email: String,
/// Receive newsletter
subscribe: bool,
}
fn contact_form_view() -> impl View {
let form_binding = ContactForm::binding();
let projected = form_binding.project();
vstack((
form(&form_binding),
text(projected.name.map(|n| format!("Hello, {}!", n))),
text(projected.email.map(|e| format!("Email: {}", e))),
))
}
Use SecureField for sensitive data with automatic memory zeroing:
use waterui::prelude::*;
use waterui_form::secure::{Secure, SecureField, secure};
fn password_form() -> impl View {
let password = Binding::new(Secure::default());
let confirm = Binding::new(Secure::default());
vstack((
SecureField::new("Password", &password),
secure("Confirm Password", &confirm),
button("Create Account", move || {
let hash = password.get().hash();
tracing::debug!("Password hash: {}", hash);
}),
))
}
The Secure type:
Zeroize to clear memory on dropDebug impl that prints Secure(****) instead of the actual valuehash() method using bcrypt with default costexpose() to access the underlying string when neededCompose validators to enforce rules on form fields:
use waterui::prelude::*;
use waterui_form::valid::{Validator, ValidatableView};
use regex::Regex;
fn validated_form() -> impl View {
let age_binding = Binding::new(0i32);
let email_binding = Binding::new(String::new());
let age_range = 18..=100;
let email_pattern = Regex::new(r"^[^@]+@[^@]+\.[^@]+$").unwrap();
vstack((
ValidatableView::new(
Stepper::new(&age_binding).label("Age"),
age_range,
),
ValidatableView::new(
TextField::new(&email_binding).label("Email"),
email_pattern,
),
))
}
Validators can be combined:
.and(other) - both must succeed.or(other) - at least one must succeedBuilt-in validators:
Range<T> - validates value is within rangeRegex - validates string matches patternRequired - validates Option<T> is Some or string is non-emptyUse DatePicker for date and time selection:
use waterui::prelude::*;
use waterui_form::picker::{DatePicker, DatePickerType};
use time::Date;
fn event_form() -> impl View {
let event_date = Binding::new(Date::MIN);
vstack((
DatePicker::new(&event_date)
.label("Event Date")
.ty(DatePickerType::DateHourAndMinute),
DatePicker::new(&event_date)
.label("Date Only")
.ty(DatePickerType::Date),
))
}
Available picker types:
DatePickerType::Date - Date onlyDatePickerType::HourAndMinute - Time only (hour:minute)DatePickerType::HourMinuteAndSecond - Time with secondsDatePickerType::DateHourAndMinute - Date and time (default)DatePickerType::DateHourMinuteAndSecond - Date and time with secondsUse ColorPicker for platform-native color selection:
use waterui::prelude::*;
use waterui_form::picker::ColorPicker;
use waterui_color::Color;
fn theme_editor() -> impl View {
let primary_color = Binding::new(Color::rgb(0.0, 0.5, 1.0));
let background = Binding::new(Color::rgb(1.0, 1.0, 1.0));
vstack((
ColorPicker::new(&primary_color).label("Primary Color"),
ColorPicker::new(&background).label("Background"),
))
}
Use MultiDatePicker for selecting multiple dates:
use waterui::prelude::*;
use waterui_form::picker::multi_date::MultiDatePicker;
use alloc::collections::BTreeSet;
use time::Date;
fn availability_calendar() -> impl View {
let available_dates = Binding::new(BTreeSet::<Date>::new());
vstack((
MultiDatePicker::new(&available_dates)
.label("Select Available Dates"),
text(available_dates.map(|dates| {
format!("Selected {} dates", dates.len())
})),
))
}
For custom layouts or specialized behavior, implement FormBuilder manually:
use waterui::prelude::*;
use waterui_form::FormBuilder;
struct TwoColumnForm {
left_field: String,
right_field: String,
}
impl FormBuilder for TwoColumnForm {
type View = HStack<(TextField, TextField)>;
fn view(binding: &Binding<Self>, _label: AnyView, _placeholder: Str) -> Self::View {
let projected = binding.project();
hstack((
TextField::new(&projected.left_field).label("Left"),
TextField::new(&projected.right_field).label("Right"),
))
}
}
FormBuilder - Trait for types that can render as form UIform() - Function to create a form view from a Binding<T: FormBuilder>Secure - Wrapper type for sensitive strings with automatic memory zeroingSecureField - Password input componentsecure() - Helper function to create a SecureFieldPicker - Generic picker for selecting from a listColorPicker - Platform-native color selectionDatePicker - Date and time selection with multiple stylesMultiDatePicker - Multiple date selectionPickerItem<T> - Type alias for picker items (TaggedView<T, Text>)Validatable - Trait for views that can be validatedValidator<T> - Trait for value validation logicValidatableView<V, T> - Wraps a view with a validatorAnd<A, B> - Combines validators with logical ANDOr<A, B> - Combines validators with logical ORRequired - Validator for required fieldsOutOfRange<T> - Error type for range validationNotMatch - Error type for regex validationNone. The crate works out-of-the-box with no feature flags.
serde - Enables Serialize and Deserialize derives on form types
waterui-form = { version = "0.1.0", features = ["serde"] }
waterui-core - Provides View trait, Binding, Environment, and AnyViewwaterui-controls - Base components (TextField, Toggle, Stepper, Slider, Button)waterui-layout - Layout primitives (VStack, HStack, ZStack)waterui-text - Text rendering componentswaterui-color - Color type used by ColorPickernami - Fine-grained reactivity system (provides Binding, Computed)time - Date/time types for DatePickerzeroize - Secure memory zeroing for Secure typebcrypt - Password hashing for Secure::hash()regex - Pattern matching for validationThe FormBuilder derive macro is provided by waterui-macros, which is automatically included when using waterui::prelude::*.
#![no_std] compatible (uses extern crate alloc)