[Help] Prevent ion-content from scrolling on input focus in Ionic

Hello everyone! :waving_hand:

I’ve created a shared layout template for my Ionic pages. Most of my pages have very little content, so my goal is to avoid any scrolling behavior altogether.

To achieve this, the simplest solution I found was to set scrollY="false" on <ion-content>. It works to prevent scrolling in general, but when scrollY is set to false and an <input> gets focused, the page scrolls automatically — which breaks the layout by pushing content out of view (especially on mobile).

Is there a better or more recommended way to prevent this automatic scroll on input focus, ideally without having to disable scroll completely?

Any tips or best practices would be greatly appreciated. Thanks in advance!

My example:

layout.component.html

<ion-header class="ion-no-border">
  <ion-toolbar [color]="toolbarColor">
    @if (backbuttonHref) {
      <ion-buttons slot="start">
        <ion-back-button [defaultHref]="backbuttonHref"></ion-back-button>
      </ion-buttons>
    }
    <ion-title>{{ title }}</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true" class="ion-padding" [scrollY]="scrollY">
  <ion-grid fixed>
    <ion-row class="ion-justify-content-center">
      <ion-col size="12" size-md="8" size-lg="6" size-xl="4">
        <div class="ion-text-center ion-margin-bottom">
          <ion-icon
            [name]="iconName"
            [color]="iconColor"
            [style.font-size]="iconSize">
          </ion-icon>
          <h2>{{ heading }}</h2>
          @if (description) {
            <ion-text color="medium">
              <p>{{ description }}</p>
            </ion-text>
          }
        </div>

        <ng-content></ng-content>

      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>

layout.component.ts

import {Component, OnInit, Input} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {
  IonBackButton, IonButtons,
  IonCol,
  IonContent,
  IonGrid,
  IonHeader,
  IonIcon, IonRow, IonText, IonTitle, IonToolbar
} from "@ionic/angular/standalone";
import {addIcons} from "ionicons";
import {
  mailOutline,
  lockClosedOutline,
  personOutline,
  personAddOutline,
  homeOutline,
  settingsOutline,
  notificationsOutline,
  heartOutline,
  starOutline,
  checkmarkCircleOutline,
  alertCircleOutline,
  informationCircleOutline,
  warningOutline,
  shieldCheckmarkOutline
} from "ionicons/icons";

@Component({
  selector: 'app-layout',
  templateUrl: './layout.component.html',
  styleUrls: ['./layout.component.scss'],
  imports: [
    FormsModule,
    IonCol,
    IonContent,
    IonGrid,
    IonHeader,
    IonIcon,
    IonRow,
    IonText,
    IonTitle,
    IonToolbar,
    ReactiveFormsModule,
    IonBackButton,
    IonButtons
  ]
})
export class LayoutComponent {
  @Input() title: string = '';
  @Input() iconName: string = '';
  @Input() heading: string = '';
  @Input() description?: string;
  @Input() toolbarColor: string = 'primary';
  @Input() iconColor: string = 'primary';
  @Input() iconSize: string = '64px';
  @Input() scrollY = false;
  @Input() backbuttonHref?: string;

  constructor() {
    addIcons({
      mailOutline,
      lockClosedOutline,
      personOutline,
      personAddOutline,
      homeOutline,
      settingsOutline,
      notificationsOutline,
      heartOutline,
      starOutline,
      checkmarkCircleOutline,
      alertCircleOutline,
      informationCircleOutline,
      warningOutline,
      shieldCheckmarkOutline
    });
  }
}

forgot-password.page.html

<app-layout
  title="Recuperar Senha"
  heading="Esqueceu sua senha?"
  description="Não se preocupe! Insira seu e-mail abaixo e nós enviaremos um link para você criar uma nova senha."
  iconName="mail-outline"
  backbuttonHref="/login"
>
  <form [formGroup]="forgotPasswordForm" (ngSubmit)="onForgotPasswordSubmit()">
    <ion-list lines="full" class="ion-no-margin ion-no-padding">
      <ion-item>
        <ion-input
          label="E-mail"
          labelPlacement="floating"
          formControlName="email"
          type="email"
          placeholder="seuemail@exemplo.com"
          required
          errorText="Por favor, insira um e-mail válido.">
        </ion-input>
      </ion-item>

    </ion-list>
    
    <ion-button
      expand="block"
      type="submit"
      class="ion-margin-top"
      [disabled]="isSubmitting">
      <ion-spinner *ngIf="isSubmitting" name="crescent"></ion-spinner>
      <span *ngIf="!isSubmitting">Enviar Link de Recuperação</span>
    </ion-button>
  </form>
</app-layout>

forgot-password.component.ts

import {Component, OnInit, inject} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
import {Router} from '@angular/router';
import {
  IonList,
  IonItem,
  IonInput,
  IonButton,
  IonSpinner
} from '@ionic/angular/standalone';
import {mailOutline} from 'ionicons/icons';
import {addIcons} from 'ionicons';
import {UiService} from "../../../core/services/ui.service";
import {AuthService} from "../../../core/services/auth.service";
import {LayoutComponent} from "../../../shared/layout/layout.component";

@Component({
  selector: 'app-forgot-password',
  templateUrl: './forgot-password.page.html',
  styleUrls: ['./forgot-password.page.scss'],
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    IonList,
    IonItem,
    IonInput,
    IonButton,
    IonSpinner,
    LayoutComponent
  ],
  providers: [AuthService, UiService]
})
export class ForgotPasswordPage implements OnInit {

  forgotPasswordForm!: FormGroup;
  isSubmitting = false;

  private fb = inject(FormBuilder);
  private router = inject(Router);
  private authService = inject(AuthService);
  private uiService = inject(UiService);

  constructor() {
    addIcons({mailOutline});
  }

  ngOnInit() {
    this.initializeForm();
  }

  private initializeForm(): void {
    this.forgotPasswordForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
    });
  }

  get email() {
    return this.forgotPasswordForm.get('email');
  }

  async onForgotPasswordSubmit(): Promise<void> {
    if (this.forgotPasswordForm.invalid) {
      this.forgotPasswordForm.markAllAsTouched();
      return;
    }

    this.isSubmitting = true;

    await this.uiService.presentLoading('Enviando código para redefinição de senha...');

    this.authService.forgotPassword(this.forgotPasswordForm.value).subscribe({
        next: () => {
          this.isSubmitting = false;
          this.uiService.dismissLoading();
          this.router.navigate(['/reset-password', this.email?.value], {replaceUrl: true});
        },
        error: err => {
          this.isSubmitting = false;
          this.uiService.dismissLoading();
          this.uiService.presentToast(
            err.message || 'Falha ao enviar código de redefinição. Verifique se o e-mail está correto.',
            'danger'
          );
        }
      }
    )
  }
}

Screenshots


This is most likely do to the keyboard offset. Ionic adds padding at the bottom to account for the keyboard being open so the user can still see the inputs (causing scrolling)

One way to disable this is to force an offset of 0 on ion-content.

<ion-content style="--keyboard-offset: 0 !important">