Forms
Ionic Forms are Angular / React / Vue forms wrapped in ion-input, ion-textarea, ion-select, ion-toggle, etc. They render native-feeling on iOS and Android, with shared validation logic.
Reactive form + validation + submission
EXAMPLE
<!-- 1) Angular — reactive form -->
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<ion-item lines="inset">
<ion-label position="floating">Email</ion-label>
<ion-input type="email" formControlName="email" autocomplete="email"></ion-input>
</ion-item>
<ion-note color="danger" *ngIf="email.invalid && email.touched">
Please enter a valid email.
</ion-note>
<ion-item>
<ion-label position="floating">Password</ion-label>
<ion-input type="password" formControlName="password"></ion-input>
</ion-item>
<ion-note color="danger" *ngIf="password.invalid && password.touched">
Min 8 characters required.
</ion-note>
<ion-item>
<ion-label>Newsletter</ion-label>
<ion-toggle formControlName="newsletter" slot="end"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Country</ion-label>
<ion-select formControlName="country" interface="action-sheet">
<ion-select-option value="au">Australia</ion-select-option>
<ion-select-option value="us">United States</ion-select-option>
<ion-select-option value="gb">UK</ion-select-option>
</ion-select>
</ion-item>
<ion-button expand="block" type="submit" [disabled]="signupForm.invalid">
Sign up
</ion-button>
</form>
// signup.page.ts
import { FormBuilder, Validators } from '@angular/forms';
export class SignupPage {
signupForm = this.fb.nonNullable.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
newsletter: [true],
country: ['au', Validators.required],
});
constructor(private fb: FormBuilder, private toast: ToastController) {}
get email() { return this.signupForm.controls.email; }
get password() { return this.signupForm.controls.password; }
async onSubmit() {
if (this.signupForm.invalid) return;
try {
await this.api.signup(this.signupForm.getRawValue());
(await this.toast.create({ message: 'Welcome!', duration: 2000 })).present();
} catch (e: any) {
(await this.toast.create({ message: e.message, color: 'danger', duration: 3000 })).present();
}
}
}
<!-- 2) React (Ionic React) — controlled inputs -->
export function Signup() {
const [email, setEmail] = useState('');
const [pw, setPw] = useState('');
const valid = email.includes('@') && pw.length >= 8;
return (
<form onSubmit={(e) => { e.preventDefault(); api.signup({ email, password: pw }); }}>
<IonItem>
<IonLabel position="floating">Email</IonLabel>
<IonInput value={email} onIonInput={(e) => setEmail(e.detail.value!)} />
</IonItem>
<IonItem>
<IonLabel position="floating">Password</IonLabel>
<IonInput type="password" value={pw} onIonInput={(e) => setPw(e.detail.value!)} />
</IonItem>
<IonButton expand="block" type="submit" disabled={!valid}>Sign up</IonButton>
</form>
);
}
Why it matters
Use ion-select interface=\"action-sheet\" on mobile — the default popover behaves oddly with the soft keyboard. Test your forms with the on-screen keyboard open, not just on a desktop browser.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
<form (ngSubmit)="save()" [formGroup]="f">
<ion-item>
<ion-label position="floating">Email</ion-label>
<ion-input type="email" formControlName="email"></ion-input>
</ion-item>
<ion-button type="submit">Save</ion-button>
</form>
Try it Yourself »
Discussion
Loading…