Form Basics
Forms are the cornerstone of interactive web applications. Whether you're building simple contact forms, complex data-entry systems, or dynamic configuration interfaces, Aurelia provides a comprehensive and performant forms system.
Quick Navigation
Basic Inputs - Text, textarea, number, date inputs
Collections - Checkboxes, radios, multi-select, arrays
Form Submission - Submit forms, handle events
File Uploads - Handle file inputs and uploads
Validation Plugin - Integrate with @aurelia/validation
Understanding Aurelia's Form Architecture
Aurelia's forms system is built on sophisticated observer patterns that provide automatic synchronization between your view models and form controls.
Data Flow Architecture
User Input → DOM Event → Observer → Binding → View Model → Reactive Updates
↑ ↓
Form Element ← DOM Update ← Binding ← Property Change ← View ModelKey Components:
Observers: Monitor DOM events and property changes
Bindings: Connect observers to view model properties
Collection Observers: Handle arrays, Sets, and Maps efficiently
Mutation Observers: Track dynamic DOM changes
Value Converters & Binding Behaviors: Transform and control data flow
Automatic Change Detection
Aurelia automatically observes:
Text inputs:
input,change,keyupeventsCheckboxes/Radio:
changeevents with array synchronizationSelect elements:
changeevents with mutation observationCollections: Array mutations, Set/Map changes
Object properties: Deep property observation
This means you typically don't need manual event handlers—Aurelia handles the complexity automatically while providing hooks for customization when needed.
Basic Input Binding
Aurelia provides intuitive two-way binding for all standard form elements. Let's start with the fundamentals.
Simple Text Inputs
The foundation of most forms is text input binding:
<form submit.trigger="handleSubmit()">
<div class="form-group">
<label for="email">Email:</label>
<input id="email"
type="email"
value.bind="email"
placeholder.bind="emailPlaceholder" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<input id="password"
type="password"
value.bind="password" />
</div>
<button type="submit" disabled.bind="!isFormValid">Login</button>
</form>export class LoginComponent {
email = '';
password = '';
emailPlaceholder = 'Enter your email address';
get isFormValid(): boolean {
return this.email.length > 0 && this.password.length >= 8;
}
handleSubmit() {
if (this.isFormValid) {
console.log('Submitting:', { email: this.email, password: this.password });
}
}
}Key points:
Use
value.bindfor two-way bindingForm inputs default to two-way binding automatically
Computed properties (like
isFormValid) automatically update
Textarea Binding
Textareas work identically to text inputs:
<div class="form-group">
<label for="comments">Comments:</label>
<textarea id="comments"
value.bind="comments"
rows="4"
maxlength.bind="maxCommentLength"></textarea>
<small>${comments.length}/${maxCommentLength} characters</small>
</div>export class FeedbackForm {
comments = '';
maxCommentLength = 500;
}Number and Date Inputs
For specialized input types, Aurelia handles type coercion automatically:
<div class="form-group">
<label for="age">Age:</label>
<input id="age"
type="number"
value.bind="age"
min="18"
max="120" />
</div>
<div class="form-group">
<label for="birthdate">Birth Date:</label>
<input id="birthdate"
type="date"
value.bind="birthDate" />
</div>
<div class="form-group">
<label for="appointment">Appointment Time:</label>
<input id="appointment"
type="datetime-local"
value.bind="appointmentTime" />
</div>export class ProfileForm {
age: number = 25;
birthDate: Date = new Date('1998-01-01');
appointmentTime: Date = new Date();
get isAdult(): boolean {
return this.age >= 18;
}
}Input Types and Binding
Aurelia supports all HTML5 input types:
text
string
General text input
email
string
Email addresses
password
string
Password fields
number
number
Numeric input
tel
string
Phone numbers
url
string
Website URLs
search
string
Search queries
date
Date/string
Date selection
time
string
Time selection
datetime-local
Date/string
Date and time
color
string
Color picker
range
number
Slider input
Binding Modes
While value.bind is automatic two-way binding, you can be explicit:
<!-- Two-way binding (default for inputs) -->
<input value.two-way="username">
<!-- One-way (view model → view) -->
<input value.one-way="displayName">
<!-- From view (view → view model) -->
<input value.from-view="searchQuery">
<!-- One-time (set once, no updates) -->
<input value.one-time="initialValue">When to use each:
.bind- Default, use for most form inputs.two-way- Explicit two-way, same as.bindfor inputs.one-way- Read-only inputs, display-only values.from-view- Capture input without updating view.one-time- Static initial values
Real-World Example
Here's a complete user registration form:
<form submit.trigger="register()" class="registration-form">
<h2>Create Account</h2>
<!-- Username -->
<div class="form-group">
<label for="username">Username *</label>
<input id="username"
type="text"
value.bind="form.username"
required
minlength="3"
maxlength="20">
<small>3-20 characters</small>
</div>
<!-- Email -->
<div class="form-group">
<label for="email">Email *</label>
<input id="email"
type="email"
value.bind="form.email"
required>
</div>
<!-- Password -->
<div class="form-group">
<label for="password">Password *</label>
<input id="password"
type="password"
value.bind="form.password"
required
minlength="8">
<small>At least 8 characters</small>
</div>
<!-- Confirm Password -->
<div class="form-group">
<label for="confirmPassword">Confirm Password *</label>
<input id="confirmPassword"
type="password"
value.bind="form.confirmPassword"
required>
<span if.bind="form.password !== form.confirmPassword" class="error">
Passwords must match
</span>
</div>
<!-- Age -->
<div class="form-group">
<label for="age">Age</label>
<input id="age"
type="number"
value.bind="form.age"
min="13"
max="120">
</div>
<!-- Bio -->
<div class="form-group">
<label for="bio">Bio</label>
<textarea id="bio"
value.bind="form.bio"
maxlength="500"
rows="4"></textarea>
<small>${form.bio.length}/500 characters</small>
</div>
<!-- Submit -->
<button type="submit"
disabled.bind="!isFormValid"
class="btn-primary">
Create Account
</button>
</form>export class Registration {
form = {
username: '',
email: '',
password: '',
confirmPassword: '',
age: null,
bio: ''
};
get isFormValid(): boolean {
return this.form.username.length >= 3 &&
this.form.email.includes('@') &&
this.form.password.length >= 8 &&
this.form.password === this.form.confirmPassword;
}
register() {
if (this.isFormValid) {
console.log('Registering user:', this.form);
// API call here
}
}
}Next Steps
Now that you understand basic form inputs, explore:
Collections - Checkboxes, radio buttons, and multi-select
Validation Plugin - Integrate form validation
File Uploads - Handle file inputs
Form Submission - Submit and process forms
Template Recipes - Real-world examples
Quick Tips
Always use labels - Associate labels with inputs using
forattributeValidate on submit - Don't validate every keystroke unless needed
Provide feedback - Show errors clearly after user completes input
Use computed properties - Let Aurelia handle form state reactively
Keep it simple - Don't overcomplicate with manual DOM manipulation
Common Pitfalls
Forgetting
.bind- Must use.bindfor two-way bindingType mismatches - Number inputs return strings, convert if needed
Direct object mutation - Use
this.form.prop = value, notform[prop] = valueMissing labels - Always include labels for accessibility
Related Documentation
Last updated
Was this helpful?