iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Inputs

Ionic’s ion-input, ion-textarea, ion-select, ion-toggle render native-feeling inputs across iOS, Android, and the web. Pair with reactive forms for validation + state.

Inputs, textarea, select, toggle, validation

EXAMPLE
<!-- Angular template -->
<form [formGroup]="form" (ngSubmit)="submit()">
    <!-- 1) Text input — float label, helper, error states -->
    <ion-item lines="inset">
        <ion-label position="floating">Email</ion-label>
        <ion-input
            formControlName="email"
            type="email"
            inputmode="email"
            autocomplete="email"
            autocapitalize="off"
            spellcheck="false"
            clearInput
        ></ion-input>
    </ion-item>
    <ion-note color="danger" *ngIf="email.invalid && email.touched">
        Please enter a valid email.
    </ion-note>

    <!-- 2) Password input — clear button + toggle visibility -->
    <ion-item>
        <ion-label position="floating">Password</ion-label>
        <ion-input
            formControlName="password"
            [type]="showPassword ? 'text' : 'password'"
            autocomplete="current-password"
        ></ion-input>
        <ion-button fill="clear" slot="end" (click)="showPassword = !showPassword">
            <ion-icon [name]="showPassword ? 'eye-off' : 'eye'" slot="icon-only"></ion-icon>
        </ion-button>
    </ion-item>

    <!-- 3) Number input — keyboard + range -->
    <ion-item>
        <ion-label position="floating">Age</ion-label>
        <ion-input
            formControlName="age"
            type="number"
            inputmode="numeric"
            min="0"
            max="120"
        ></ion-input>
    </ion-item>

    <!-- 4) Phone -->
    <ion-item>
        <ion-label position="floating">Phone</ion-label>
        <ion-input
            formControlName="phone"
            type="tel"
            inputmode="tel"
            autocomplete="tel"
            placeholder="+61 4xx xxx xxx"
        ></ion-input>
    </ion-item>

    <!-- 5) Textarea — auto-grow optional -->
    <ion-item>
        <ion-label position="floating">Bio</ion-label>
        <ion-textarea
            formControlName="bio"
            rows="4"
            autoGrow="true"
            maxlength="500"
            counter="true"
        ></ion-textarea>
    </ion-item>

    <!-- 6) Select — single -->
    <ion-item>
        <ion-label>Country</ion-label>
        <ion-select
            formControlName="country"
            interface="action-sheet"
            placeholder="Choose a country"
        >
            <ion-select-option value="au">Australia</ion-select-option>
            <ion-select-option value="us">United States</ion-select-option>
            <ion-select-option value="uk">United Kingdom</ion-select-option>
            <ion-select-option value="de">Germany</ion-select-option>
        </ion-select>
    </ion-item>

    <!-- 7) Select — multiple -->
    <ion-item>
        <ion-label>Languages</ion-label>
        <ion-select formControlName="languages" multiple="true">
            <ion-select-option value="en">English</ion-select-option>
            <ion-select-option value="fr">French</ion-select-option>
            <ion-select-option value="es">Spanish</ion-select-option>
            <ion-select-option value="de">German</ion-select-option>
        </ion-select>
    </ion-item>

    <!-- 8) Toggle (switch) -->
    <ion-item>
        <ion-label>Notifications</ion-label>
        <ion-toggle formControlName="notifications" slot="end"></ion-toggle>
    </ion-item>

    <!-- 9) Checkbox -->
    <ion-item>
        <ion-label>I accept the terms</ion-label>
        <ion-checkbox formControlName="terms" slot="start"></ion-checkbox>
    </ion-item>

    <!-- 10) Radio group -->
    <ion-radio-group formControlName="plan">
        <ion-list-header><ion-label>Plan</ion-label></ion-list-header>
        <ion-item>
            <ion-label>Free</ion-label>
            <ion-radio slot="start" value="free"></ion-radio>
        </ion-item>
        <ion-item>
            <ion-label>Pro</ion-label>
            <ion-radio slot="start" value="pro"></ion-radio>
        </ion-item>
        <ion-item>
            <ion-label>Enterprise</ion-label>
            <ion-radio slot="start" value="enterprise"></ion-radio>
        </ion-item>
    </ion-radio-group>

    <!-- 11) Range -->
    <ion-item>
        <ion-label>Volume</ion-label>
        <ion-range formControlName="volume" min="0" max="100" step="5" pin="true">
            <ion-icon slot="start" name="volume-low"></ion-icon>
            <ion-icon slot="end" name="volume-high"></ion-icon>
        </ion-range>
    </ion-item>

    <!-- 12) DateTime input — modal picker -->
    <ion-item button id="open-datetime">
        <ion-label>Birth date</ion-label>
        <ion-text slot="end">{{ form.value.birthDate | date }}</ion-text>
    </ion-item>

    <ion-modal trigger="open-datetime">
        <ng-template>
            <ion-datetime
                formControlName="birthDate"
                presentation="date"
                [max]="today"
                [min]="'1900-01-01'"
            ></ion-datetime>
        </ng-template>
    </ion-modal>

    <!-- 13) Submit -->
    <div class="ion-padding">
        <ion-button expand="block" type="submit" [disabled]="form.invalid || busy">
            <ion-spinner *ngIf="busy" slot="start"></ion-spinner>
            {{ busy ? 'Saving…' : 'Sign up' }}
        </ion-button>
    </div>
</form>

<!-- ====================== TypeScript ====================== -->

// signup.page.ts
import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { ToastController } from '@ionic/angular';

@@Component({ selector: 'app-signup', templateUrl: './signup.page.html' })
export class SignupPage {
    showPassword = false;
    busy = false;
    today = new Date().toISOString();

    form = this.fb.group({
        email:        ['', [Validators.required, Validators.email]],
        password:     ['', [Validators.required, Validators.minLength(8)]],
        age:          [0,  [Validators.min(0),    Validators.max(120)]],
        phone:        [''],
        bio:          [''],
        country:      [null, Validators.required],
        languages:    [[]],
        notifications: [true],
        terms:        [false, Validators.requiredTrue],
        plan:         ['free'],
        volume:       [50],
        birthDate:    [null],
    });

    constructor(private fb: FormBuilder, private toast: ToastController) {}

    get email()    { return this.form.controls.email; }
    get password() { return this.form.controls.password; }

    async submit() {
        if (this.form.invalid) return;
        this.busy = true;
        try {
            await this.api.signup(this.form.getRawValue());
            (await this.toast.create({ message: 'Account created', duration: 2000 })).present();
        } catch (e: any) {
            (await this.toast.create({ message: e.message, color: 'danger', duration: 3000 })).present();
        } finally {
            this.busy = false;
        }
    }
}

<!-- ====================== React (Ionic React) ====================== -->

import { useState } from 'react';
import { IonInput, IonItem, IonLabel, IonNote, IonButton } from '@ionic/react';

function SignupForm() {
    const [email, setEmail] = useState('');
    const [touched, setTouched] = useState(false);
    const error = touched && !/\S+@@\S+\.\S+/.test(email) ? 'Invalid email' : '';

    return (
        <>
            <IonItem>
                <IonLabel position="floating">Email</IonLabel>
                <IonInput
                    value={email}
                    onIonInput={(e) => setEmail(e.detail.value!)}
                    onIonBlur={() => setTouched(true)}
                    type="email"
                    autocomplete="email"
                />
            </IonItem>
            {error && <IonNote color="danger">{error}</IonNote>}
            <IonButton expand="block" disabled={!!error || !email}>Sign up</IonButton>
        </>
    );
}

<!-- ====================== Best practices ====================== -->

<!-- ✅ inputmode + type for the right native keyboard -->
<!--    type="email"  → email keyboard -->
<!--    type="tel"     → number pad -->
<!--    inputmode="decimal" → numeric with dot for prices -->
<!--    type="search" → keyboard with 'search' button -->

<!-- ✅ autocomplete + autocapitalize hints for OS suggestions / autofill -->
<!--    autocomplete="email | password | new-password | tel | given-name | ..." -->
<!--    autocapitalize="off" on email + passwords -->

<!-- ✅ Validation -->
<!--    Pair Reactive Forms with native input validators -->
<!--    Show errors only after blur (touched) — not on first render -->

<!-- ✅ Accessibility -->
<!--    ion-label for every input -->
<!--    aria-describedby for error notes -->
<!--    aria-invalid when applicable -->
<!--    Sufficient color contrast on errors -->

<!-- ✅ Loading + disabled states -->
<!--    Disable submit during await -->
<!--    Spinner inside the button -->

<!-- ❌ Common bugs -->
<!--    Wrong type → wrong keyboard -->
<!--    Missing autocomplete → no autofill -->
<!--    Forgetting ion-label → screen reader silent -->
<!--    Showing errors immediately → bad UX on first render -->
<!--    Capturing onIonChange instead of onIonInput → 1-update delay -->

Why it matters

Match type + inputmode + autocomplete to the data — users get the right keyboard, the right autofill, and the right autocapitalisation. Reactive Forms + show-errors-after-blur is the standard Angular validation pattern.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<ion-item>
    <ion-label>Name</ion-label>
    <ion-input [(ngModel)]="name"></ion-input>
</ion-item>
<ion-toggle [(ngModel)]="darkMode">Dark mode</ion-toggle>
Try it Yourself »

Discussion

Loading…