ionic-team/ionic-framework · error · Error

IonTabs cannot contain an IonRouterOutlet and IonTab at the

Error message

IonTabs cannot contain an IonRouterOutlet and IonTab at the same time.

What it means

Thrown from Vue `<IonTabs>` render() when the slot contains BOTH an `IonRouterOutlet` and an `IonTab`. These are mutually exclusive navigation models: IonTabs must use either the router outlet (URL/history-based) OR plain `IonTab` children (router-less), never both. The check exists to prevent ambiguous tab state.

Source

Thrown at packages/vue/src/components/IonTabs.ts:144

      routerOutlet = slottedContent.find((child: VNode) =>
        isRouterOutlet(child)
      );

      /**
       * Developers must pass at least one ion-tab
       * inside of ion-tabs if they want to use a
       * basic tab-based navigation without the
       * history stack or URL updates associated
       * with the router.
       */
      hasTab = slottedContent.some((child: VNode) => isTab(child));
    }

    if (!routerOutlet && !hasTab) {
      throw new Error("IonTabs must contain an IonRouterOutlet or an IonTab.");
    }
    if (routerOutlet && hasTab) {
      throw new Error(
        "IonTabs cannot contain an IonRouterOutlet and IonTab at the same time."
      );
    }

    if (hasTab) {
      return h(
        "ion-tabs",
        {
          ...props,
        },
        slottedContent
      );
    }

    /**
     * TODO(ROU-11056)
     *
     * Vue handles the error case for when there is no

View on GitHub (pinned to 625f9c38ad)

Solutions

  1. Pick one navigation model: keep `<IonRouterOutlet>` and remove all `<IonTab>` children, OR keep `<IonTab>` children and remove the outlet.
  2. Search the `<IonTabs>` template for `IonTab`/`ion-tab` and `IonRouterOutlet`/`ion-router-outlet` and remove the unwanted one.
  3. If using router-less tabs, drive tab content via `<IonTab>` components and an `<IonTabBar>` without an outlet.
  4. If using router tabs, declare routes inside `<IonRouterOutlet>` and do not author `<IonTab>` elements.

Example fix

<!-- before (both present) -->
<IonTabs>
  <IonRouterOutlet>...</IonRouterOutlet>
  <IonTab tab="home">...</IonTab>
</IonTabs>
<!-- after (router model only) -->
<IonTabs>
  <IonRouterOutlet>...</IonRouterOutlet>
  <IonTabBar slot="bottom">...</IonTabBar>
</IonTabs>
Defensive patterns

Strategy: type-guard

Validate before calling

const outlet = slotted?.some((n) => n?.type && ((n.type as any).name === 'IonRouterOutlet' || n.type === 'ion-router-outlet'));
const tab = slotted?.some((n) => n?.type && ((n.type as any).name === 'ion-tab' || n.type === 'IonTab'));
if (outlet && tab) {
  throw new Error('Choose one: IonRouterOutlet (router tabs) OR IonTab (router-less tabs), not both.');
}

Type guard

function isExclusiveTabsModel(slotted: any[] | undefined): boolean {
  const outlet = !!slotted?.some((n) => n?.type && ((n.type as any).name === 'IonRouterOutlet' || n.type === 'ion-router-outlet'));
  const tab = !!slotted?.some((n) => n?.type && ((n.type as any).name === 'ion-tab' || n.type === 'IonTab'));
  return outlet !== tab; // true only when exactly one is present
}

Prevention

When it happens

Trigger: Authoring `<IonTabs>` that simultaneously includes `<IonRouterOutlet>` (or `ion-router-outlet`) and one or more `<IonTab>` (`ion-tab`) descendants anywhere in the slotted VNodes, including inside a Fragment/v-for.

Common situations: Partially migrating from router-less tabs to router tabs and leaving an `<IonTab>` behind; combining a router outlet with manually-declared tab children in the same template; tabs generated from a list where both shapes are present.

Related errors


AI-assisted analysis of ionic-team/ionic-framework@625f9c38ad (2026-08-12). Data as JSON: /api/errors/b91d7456be7f2b43. Report an issue: GitHub.