ionic-team/ionic-framework · error · Error

IonTabs cannot contain an IonRouterOutlet and an IonTab at t

Error message

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

What it means

Thrown during React `<IonTabs>` render when children contain BOTH an `IonRouterOutlet` (by type or `isRouterOutlet`) and an `IonTab`. The two are mutually exclusive navigation models; the component rejects the combination to avoid ambiguous tab state and broken routing.

Source

Thrown at packages/react/src/components/navigation/IonTabs.tsx:181

          onIonTabsDidChange,
        };
      }

      if (onIonTabsWillChange !== undefined) {
        childProps = {
          ...childProps,
          onIonTabsWillChange,
        };
      }

      this.ionTabContextState.tabBarProps = childProps;
    });

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

    if (hasTab) {
      return <IonTabsInner {...this.props}></IonTabsInner>;
    }

    /**
     * TODO(ROU-11051)
     *
     * There is no error handling for the case where there
     * is no associated Route for the given IonTabButton.
     *
     * More investigation is needed to determine how to
     * handle this to prevent any overwriting of the
     * IonTabButton's onClick handler and how the routing
     * is handled.
     */

View on GitHub (pinned to 625f9c38ad)

Solutions

  1. Choose one model: keep `<IonRouterOutlet>` and remove all `<IonTab>` children, or keep `<IonTab>` children and remove the outlet.
  2. Search the `<IonTabs>` subtree for `IonRouterOutlet` and `IonTab` and remove the unwanted one.
  3. For router-less tabs use `<IonTab>` + `<IonTabBar>` without an outlet.
  4. For router tabs use `<IonRouterOutlet>` + `<Route>` and do not declare `<IonTab>` elements.

Example fix

// before (both present)
<IonTabs>
  <IonRouterOutlet><Route path="/home" component={Home} /></IonRouterOutlet>
  <IonTab tab="home"><Home /></IonTab>
</IonTabs>
// after (router model only)
<IonTabs>
  <IonRouterOutlet><Route path="/home" component={Home} /></IonRouterOutlet>
  <IonTabBar slot="bottom"><IonTabButton tab="home" href="/home"><IonLabel>Home</IonLabel></IonTabButton></IonTabBar>
</IonTabs>
Defensive patterns

Strategy: validation

Validate before calling

let outlet = false, hasTab = false;
React.Children.forEach(children, (child: any) => {
  if (child?.type === IonRouterOutlet || child?.type?.isRouterOutlet) outlet = true;
  else if (child?.type === IonTab) hasTab = true;
});
if (outlet && hasTab) {
  throw new Error('Choose one: IonRouterOutlet (router tabs) OR IonTab (router-less tabs).');
}

Type guard

function isExclusiveTabsModel(children: React.ReactNode): boolean {
  let outlet = false, hasTab = false;
  React.Children.forEach(children, (child: any) => {
    if (child?.type === IonRouterOutlet || child?.type?.isRouterOutlet) outlet = true;
    else if (child?.type === IonTab) hasTab = true;
  });
  return outlet !== hasTab;
}

Prevention

When it happens

Trigger: Authoring `<IonTabs>` with both `<IonRouterOutlet>` and one or more `<IonTab>` components among the children (directly or inside a fragment whose first child is an outlet).

Common situations: Mid-migration between router-less and router-based tabs leaving both shapes in place; copy-pasting tab snippets that mix patterns; rendering tabs from a list where both an outlet and tab elements are present.

Related errors


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