Menú lateral en Ionic 8: ion-menu paso a paso (2026)

📘 ¿Quieres una base sólida de Ionic desde cero? Si prefieres aprender Ionic (y Firebase) de forma ordenada y de principio a fin, en lugar de ir saltando entre tutoriales sueltos, te dejo mi libro: Desarrollo de aplicaciones móviles con Ionic y Firebase desde cero.

Para crear un menú lateral en Ionic 8 colocas un componente <ion-menu> dentro de un <ion-split-pane>, lo conectas al ion-router-outlet mediante el atributo contentId y lo abres desde cualquier página con un <ion-menu-button>. Es el patrón clásico de navegación con menú deslizante (el típico botón de «hamburguesa» ☰) y en esta guía lo montamos paso a paso con el enfoque moderno de componentes standalone, que es como se trabaja hoy en Ionic.

Actualizado: julio 2026 — revisado con Ionic 8.8 y Angular standalone.

En el capítulo de navegación vimos cómo movernos entre páginas con rutas. El menú lateral es el siguiente paso natural: un cajón lateral desde el que el usuario salta a las secciones principales de la app. Aquí tienes el índice completo del tutorial:

Qué es el menú lateral en Ionic (ion-menu y split-pane)

Ionic resuelve el menú lateral con dos componentes que trabajan juntos:

  • ion-menu: el propio cajón deslizante. Se abre desde un lado de la pantalla (por defecto, el izquierdo) y contiene la lista de secciones.
  • ion-split-pane: un contenedor que, en pantallas anchas (tablet, escritorio), deja el menú fijo a la izquierda en lugar de deslizante. En el móvil se comporta como un menú normal; en escritorio, como una barra lateral permanente. Con un solo componente cubres los dos casos.

La pieza clave es el atributo contentId: le dice al menú a qué contenido tiene que empujar cuando se abre. Ese contenido suele ser el ion-router-outlet, donde se renderizan tus páginas.

Crear el proyecto de partida

Arrancamos con la plantilla blank. Desde Ionic 7 el CLI genera proyectos standalone por defecto (sin NgModule), así que no hay que configurar nada extra:

ionic start menuApp blank --type=angular
cd menuApp

Genera un par de páginas para tener a dónde navegar desde el menú:

ionic generate page inicio
ionic generate page listado

Estructura básica: el menú en el componente raíz

El menú vive en el componente raíz de la app (app.component), porque tiene que estar disponible en todas las páginas. Empezamos por el bootstrap de la aplicación en main.ts, que es donde se registran Ionic y el router:

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideIonicAngular } from '@ionic/angular/standalone';

import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';

bootstrapApplication(AppComponent, {
  providers: [
    provideIonicAngular(),
    provideRouter(routes),
  ],
});

Ahora el componente raíz. Fíjate en dos cosas propias del enfoque standalone: cada componente de Ionic se importa uno a uno desde @ionic/angular/standalone, y los iconos se registran a mano con addIcons (ya no se cargan todos de golpe, así el bundle es más ligero):

// app.component.ts
import { Component } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { addIcons } from 'ionicons';
import { homeOutline, listOutline, settingsOutline } from 'ionicons/icons';
import {
  IonApp, IonSplitPane, IonMenu, IonHeader, IonToolbar, IonTitle,
  IonContent, IonList, IonListHeader, IonMenuToggle, IonItem,
  IonIcon, IonLabel, IonRouterOutlet,
} from '@ionic/angular/standalone';

@Component({
  selector: 'app-root',
  standalone: true,
  templateUrl: './app.component.html',
  imports: [
    RouterLink, RouterLinkActive,
    IonApp, IonSplitPane, IonMenu, IonHeader, IonToolbar, IonTitle,
    IonContent, IonList, IonListHeader, IonMenuToggle, IonItem,
    IonIcon, IonLabel, IonRouterOutlet,
  ],
})
export class AppComponent {
  // Las secciones que aparecerán en el menú
  paginas = [
    { titulo: 'Inicio',  url: '/inicio',  icono: 'home-outline' },
    { titulo: 'Listado', url: '/listado', icono: 'list-outline' },
  ];

  constructor() {
    // Registra los iconos que vas a usar (nombre que usarás en la plantilla → icono importado)
    addIcons({
      'home-outline': homeOutline,
      'list-outline': listOutline,
      'settings-outline': settingsOutline,
    });
  }
}

Y la plantilla. Aquí es donde se ve el patrón completo: split-pane > menu + router-outlet, unidos por el mismo contentId:

<!-- app.component.html -->
<ion-app>
  <ion-split-pane contentId="main-content">

    <ion-menu contentId="main-content">
      <ion-header>
        <ion-toolbar color="primary">
          <ion-title>Menú</ion-title>
        </ion-toolbar>
      </ion-header>

      <ion-content>
        <ion-list>
          <ion-list-header>Secciones</ion-list-header>

          @for (p of paginas; track p.url) {
            <ion-menu-toggle auto-hide="false">
              <ion-item
                [routerLink]="p.url"
                routerDirection="root"
                routerLinkActive="selected"
                lines="none"
                detail="false">
                <ion-icon slot="start" [name]="p.icono"></ion-icon>
                <ion-label>{{ p.titulo }}</ion-label>
              </ion-item>
            </ion-menu-toggle>
          }
        </ion-list>
      </ion-content>
    </ion-menu>

    <!-- El id DEBE coincidir con el contentId del menú y del split-pane -->
    <ion-router-outlet id="main-content"></ion-router-outlet>

  </ion-split-pane>
</ion-app>

Tres detalles que evitan el 90% de los dolores de cabeza:

  • El id="main-content" del ion-router-outlet tiene que coincidir exactamente con el contentId del menú y del split-pane. Si no coinciden, el menú no se abre y no verás ningún error.
  • El @for es la sintaxis de control de flujo moderna de Angular (17+). Sustituye al viejo *ngFor y no necesita importar CommonModule.
  • ion-menu-toggle envuelve cada opción para que el menú se cierre solo al pulsar. El auto-hide="false" mantiene visibles los enlaces aunque el menú esté fijo en escritorio.

Añadir el botón para abrir el menú

El menú ya existe, pero en el móvil necesitas un botón para abrirlo. Se coloca en la barra de herramientas de cada página con ion-menu-button. Este es el HTML de la página de inicio:

<!-- inicio.page.html -->
<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>Inicio</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <p>Soy la página de inicio. Pulsa ☰ para abrir el menú lateral.</p>
</ion-content>

Como cada página es standalone, recuerda importar en su .ts los componentes que usa la plantilla:

// inicio.page.ts
import { Component } from '@angular/core';
import {
  IonHeader, IonToolbar, IonButtons, IonMenuButton,
  IonTitle, IonContent,
} from '@ionic/angular/standalone';

@Component({
  selector: 'app-inicio',
  standalone: true,
  templateUrl: './inicio.page.html',
  imports: [
    IonHeader, IonToolbar, IonButtons, IonMenuButton,
    IonTitle, IonContent,
  ],
})
export class InicioPage {}

Con esto ya tienes un menú lateral funcionando: en el móvil se desliza al pulsar el botón; en pantalla ancha, gracias al split-pane, se queda fijo a la izquierda.

Menú fijo en escritorio: el split-pane responsive

Por defecto, el ion-split-pane muestra el menú fijo cuando la pantalla supera el breakpoint md (unos 768 px). Puedes cambiar a partir de qué tamaño se «ancla» el menú con el atributo when:

<!-- Menú fijo solo en pantallas grandes (≥ 992 px) -->
<ion-split-pane contentId="main-content" when="lg"> ... </ion-split-pane>

<!-- O con una media query personalizada -->
<ion-split-pane contentId="main-content" when="(min-width: 1024px)"> ... </ion-split-pane>

Valores admitidos: xs, sm, md, lg, xl, una media query, o directamente false si nunca quieres el menú fijo (siempre deslizante).

Abrir, cerrar y alternar el menú por código

A veces necesitas controlar el menú desde el TypeScript (por ejemplo, cerrarlo tras una acción). Para eso está MenuController. En un proyecto standalone lo importas desde @ionic/angular/standalone y lo inyectas con inject():

// cualquier.page.ts
import { Component, inject } from '@angular/core';
import { MenuController } from '@ionic/angular/standalone';

@Component({ /* ... */ })
export class ListadoPage {
  private menu = inject(MenuController);

  abrirMenu()   { this.menu.open(); }
  cerrarMenu()  { this.menu.close(); }
  alternarMenu(){ this.menu.toggle(); }
}

Si tienes varios menús (uno a cada lado, por ejemplo), dales un menuId y pásaselo a los métodos:

<ion-menu contentId="main-content" menuId="ajustes" side="end"> ... </ion-menu>
this.menu.open('ajustes');    // abre el menú de la derecha
this.menu.enable(false, 'ajustes'); // lo deshabilita (útil según el rol del usuario)

Menús condicionales: ocultar opciones según el usuario

Un caso muy habitual: mostrar unas secciones u otras según si el usuario ha iniciado sesión o su rol. Como el menú se construye desde un array, basta con filtrarlo:

paginas = [
  { titulo: 'Inicio',  url: '/inicio',  icono: 'home-outline',     soloAdmin: false },
  { titulo: 'Panel',   url: '/panel',   icono: 'settings-outline', soloAdmin: true  },
];

get paginasVisibles() {
  return this.paginas.filter(p => !p.soloAdmin || this.esAdmin);
}

Y en la plantilla recorres paginasVisibles en lugar de paginas. Sin trucos raros: el menú es UI normal de Angular.

Errores típicos (y cómo salir de ellos)

  • El menú no se abre y no hay error. Casi siempre es que contentId y el id del ion-router-outlet no coinciden. Revísalos carácter a carácter.
  • Los iconos salen en blanco. En standalone hay que registrarlos con addIcons; si te olvidas de uno, aparece hueco. Comprueba que el nombre que pones en name="..." es exactamente la clave que registraste.
  • El botón ☰ no aparece en escritorio. Es lo esperado: cuando el split-pane ancla el menú, Ionic oculta el ion-menu-button porque ya no hace falta. En móvil vuelve a mostrarse.
  • Import mal resuelto. Si mezclas imports de @ionic/angular y de @ionic/angular/standalone en el mismo proyecto standalone, tendrás comportamientos raros. En un proyecto standalone, importa todo (componentes y controladores) desde @ionic/angular/standalone.

Menú lateral en Ionic abierto sobre una app blank

Seguimos con el tutorial

Con el menú lateral ya montado, el siguiente paso lógico es dominar el otro gran patrón de navegación de Ionic: las pestañas. Lo tienes en el tutorial de Ionic tabs. Y cuando quieras conectar tu app a datos reales, sigue por peticiones HTTP a una API REST y por formularios reactivos.

📘 ¿Quieres aprender Ionic de forma ordenada, no a saltos? Todo esto (menú, tabs, HTTP, Firebase) y mucho más, paso a paso y con imágenes, en mi libro Desarrollo de aplicaciones móviles con Ionic y Firebase desde cero.

📱 ¿Prefieres que te desarrolle yo la app? Mira mi presupuesto para desarrollo de apps y cuéntame tu idea.

Un saludo, y si aún no lo has hecho no olvides suscribirte a mi blog para no perderte los próximos posts  :-),

También puedes seguirme en Twitter en ‎@revi_apps y no olvides que me ayudas mucho si compartes este post en las redes sociales.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Scroll al inicio