{"record":{"id":"56c2794fbcf711e3","repo":"ionic-team/ionic-framework","slug":"iontabs-must-contain-an-ionrouteroutlet-or-an-iont","errorCode":null,"errorMessage":"IonTabs must contain an IonRouterOutlet or an IonTab.","messagePattern":"IonTabs must contain an IonRouterOutlet or an IonTab\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vue/src/components/IonTabs.ts","lineNumber":141,"sourceCode":"       * the history stack or URL updates associated\n       * with the router.\n       */\n      routerOutlet = slottedContent.find((child: VNode) =>\n        isRouterOutlet(child)\n      );\n\n      /**\n       * Developers must pass at least one ion-tab\n       * inside of ion-tabs if they want to use a\n       * basic tab-based navigation without the\n       * history stack or URL updates associated\n       * with the router.\n       */\n      hasTab = slottedContent.some((child: VNode) => isTab(child));\n    }\n\n    if (!routerOutlet && !hasTab) {\n      throw new Error(\"IonTabs must contain an IonRouterOutlet or an IonTab.\");\n    }\n    if (routerOutlet && hasTab) {\n      throw new Error(\n        \"IonTabs cannot contain an IonRouterOutlet and IonTab at the same time.\"\n      );\n    }\n\n    if (hasTab) {\n      return h(\n        \"ion-tabs\",\n        {\n          ...props,\n        },\n        slottedContent\n      );\n    }\n\n    /**","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/ionic-team/ionic-framework/blob/625f9c38ad8c5db8a6c12df5c1622a36da90f9e3/packages/vue/src/components/IonTabs.ts#L123-L159","documentation":"Thrown from the render() of the Vue `<IonTabs>` component when the default slot contains neither an `IonRouterOutlet` nor an `IonTab` vnode. IonTabs requires exactly one of those two navigation models so it knows how to manage child views. Because this fires during render, Vue surfaces it as an uncaught runtime error.","triggerScenarios":"Authoring `<IonTabs>` whose children are only an `<IonTabBar>`, plain divs, or nothing at all — i.e. neither `<IonRouterOutlet>` nor `<IonTab>` is present in the slot.","commonSituations":"Migrating from an older tabs API that allowed a tab bar alone; forgetting to wrap routes in `<IonRouterOutlet>`; rendering tabs conditionally so the outlet is absent on first render; copy-pasting a tabs template that omitted the outlet.","solutions":["Add an `<ion-router-outlet>` (via `<IonRouterOutlet>`) as a direct child of `<IonTabs>` for router-driven tabs.","Or add at least one `<IonTab>` child for router-less tab navigation.","Ensure the outlet/tab is not gated behind a `v-if` that evaluates false on the first render; use `v-show` or render a fallback.","If the slot content comes from a dynamic list, guarantee the list is non-empty and contains a recognized tab/outlet element."],"exampleFix":"<!-- before -->\n<IonTabs>\n  <IonTabBar slot=\"bottom\">\n    <IonTabButton tab=\"home\"><IonLabel>Home</IonLabel></IonTabButton>\n  </IonTabBar>\n</IonTabs>\n<!-- after -->\n<IonTabs>\n  <IonRouterOutlet>\n    <Route path=\"/home\" component=\"Home\" />\n  </IonRouterOutlet>\n  <IonTabBar slot=\"bottom\">\n    <IonTabButton tab=\"home\" href=\"/home\"><IonLabel>Home</IonLabel></IonTabButton>\n  </IonTabBar>\n</IonTabs>","handlingStrategy":"type-guard","validationCode":"// Before relying on IonTabs, assert the slot will contain a recognized child\nimport { h, Fragment, isVNode } from 'vue';\nconst isRouterOutlet = (n: any) => n?.type && ((n.type as any).name === 'IonRouterOutlet' || n.type === 'ion-router-outlet');\nconst isTab = (n: any) => {\n  if (n?.type === Fragment && Array.isArray(n.children)) return n.children.some((c) => isVNode(c) && isTab(c));\n  return n?.type && ((n.type as any).name === 'ion-tab' || n.type === IonTab);\n};\nfunction validateTabsChildren(slotted: any[] | undefined) {\n  const hasOutlet = !!slotted?.some(isRouterOutlet);\n  const hasTab = !!slotted?.some(isTab);\n  if (!hasOutlet && !hasTab) throw new Error('IonTabs needs an IonRouterOutlet or IonTab child');\n}","typeGuard":"function hasTabsNavModel(slotted: any[] | undefined): boolean {\n  const outlet = slotted?.some((n) => n?.type && ((n.type as any).name === 'IonRouterOutlet' || n.type === 'ion-router-outlet'));\n  const tab = slotted?.some((n) => n?.type && ((n.type as any).name === 'ion-tab' || n.type === 'IonTab'));\n  return Boolean(outlet) || Boolean(tab);\n}","tryCatchPattern":null,"preventionTips":["Always include an <IonRouterOutlet> (router tabs) or at least one <IonTab> (router-less tabs) inside <IonTabs>.","Avoid v-if on the outlet/tab that can be false on first render; use v-show or a guaranteed fallback.","Keep tab templates in one place to spot missing outlets during review."],"tags":["vue","ion-tabs","navigation","render"],"backgroundTag":null,"analyzedSha":"625f9c38ad8c5db8a6c12df5c1622a36da90f9e3","analyzedAt":"2026-08-12T16:00:25.413Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}