[Vue warn]: Failed to resolve component: ion-label

I know this is a common issue, but I’ve followed the steps provided in past threads and I cannot get this warning to go away. I actually have a LOT of these warnings and I believe I’ve imported everything fine. So what’s going on?

Below is one of the starter templates I began customizing.

<template>
  <ion-app>
    <ion-split-pane content-id="main-content">
      <ion-menu content-id="main-content" type="overlay">
        <ion-content>
          <ion-list id="inbox-list">
            <ion-list-header>Hello Jonathan 👋</ion-list-header>
            <ion-note>demo@tcgsniper.com</ion-note>

            <ion-menu-toggle
              auto-hide="false"
              v-for="(p, i) in appPages"
              :key="i"
            >
              <ion-item
                button
                @click="selectedIndex = i"
                router-direction="root"
                :router-link="p.url"
                lines="none"
                detail="false"
                class="hydrated"
                :class="{ selected: selectedIndex === i }"
              >
                <ion-icon
                  slot="start"
                  :ios="p.iosIcon"
                  :md="p.mdIcon"
                ></ion-icon>
                <ion-label>{{ p.title }}</ion-label>
              </ion-item>
            </ion-menu-toggle>
          </ion-list>

          <ion-list id="labels-list">
            <ion-list-header>Need help?</ion-list-header>

            <ion-item
              button
              v-for="(label, index) in helpLabels"
              @click="selectedIndex = i"
              :router-link="label.url"
              lines="none"
              :key="index"
            >
              <ion-icon
                slot="start"
                :ios="label.iosIcon"
                :md="label.mdIcon"
              ></ion-icon>
              <ion-label>{{ label.title }}</ion-label>
            </ion-item>
          </ion-list>
        </ion-content>
      </ion-menu>
      <ion-router-outlet id="main-content"></ion-router-outlet>
    </ion-split-pane>
  </ion-app>
</template>

<script lang="ts">
import {
  IonApp,
  IonContent,
  IonIcon,
  IonItem,
  IonLabel,
  IonList,
  IonListHeader,
  IonMenu,
  IonMenuToggle,
  IonNote,
  IonRouterOutlet,
  IonSplitPane,
} from "@ionic/vue";
import { defineComponent, ref } from "vue";
import { useRoute } from "vue-router";
import {
  speedometerOutline,
  speedometerSharp,
  alertOutline,
  alertSharp,
  settingsOutline,
  settingsSharp,
  archiveOutline,
  archiveSharp,
  bookmarkOutline,
  bookmarkSharp,
  heartOutline,
  heartSharp,
  mail,
  mailSharp,
  link,
  linkSharp,
  paperPlaneOutline,
  paperPlaneSharp,
  trashOutline,
  trashSharp,
  warningOutline,
  warningSharp,
} from "ionicons/icons";

export default defineComponent({
  name: "App",
  components: {
    IonApp,
    IonContent,
    IonIcon,
    IonItem,
    IonLabel,
    IonList,
    IonListHeader,
    IonMenu,
    IonMenuToggle,
    IonNote,
    IonRouterOutlet,
    IonSplitPane,
  },
  setup() {
    const selectedIndex = ref(0);
    const appPages = [
      {
        title: "Dashboard",
        url: "/dashboard",
        iosIcon: speedometerOutline,
        mdIcon: speedometerSharp,
      },
      {
        title: "Create Alert",
        url: "/alert",
        iosIcon: alertOutline,
        mdIcon: alertSharp,
      },
      {
        title: "Account Settings",
        url: "/settings",
        iosIcon: settingsOutline,
        mdIcon: settingsSharp,
      },
    ];
    const helpLabels = [
      {
        title: "How do I create a price alert?",
        url: "https://tcgsniper.com/help/how-to-create-a-price-alert-for-tcgplayer-com/",
        iosIcon: link,
        mdIcon: linkSharp,
      },
      {
        title: "help@tcgsniper.com",
        url: "mailto:help@tcgsniper",
        iosIcon: mail,
        mdIcon: mailSharp,
      },
      {
        title: "Help Center",
        url: "https://tcgsniper.com/help/",
        iosIcon: link,
        mdIcon: linkSharp,
      },
    ];

    const path = window.location.pathname.split("/")[1];
    if (path !== undefined) {
      selectedIndex.value = appPages.findIndex(
        (page) => page.title.toLowerCase() === path.toLowerCase()
      );
    }

    const route = useRoute();

    return {
      selectedIndex,
      appPages,
      helpLabels,
      archiveOutline,
      archiveSharp,
      bookmarkOutline,
      bookmarkSharp,
      heartOutline,
      heartSharp,
      mailSharp,
      paperPlaneOutline,
      paperPlaneSharp,
      trashOutline,
      trashSharp,
      warningOutline,
      warningSharp,
      isSelected: (url: string) => (url === route.path ? "selected" : ""),
    };
  },
});
</script>

<style scoped>
ion-menu ion-content {
  --background: var(--ion-item-background, var(--ion-background-color, #fff));
}

ion-menu.md ion-content {
  --padding-start: 8px;
  --padding-end: 8px;
  --padding-top: 20px;
  --padding-bottom: 20px;
}

ion-menu.md ion-list {
  padding: 20px 0;
}

ion-menu.md ion-note {
  margin-bottom: 30px;
}

ion-menu.md ion-list-header,
ion-menu.md ion-note {
  padding-left: 10px;
}

ion-menu.md ion-list#inbox-list {
  border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
}

ion-menu.md ion-list#inbox-list ion-list-header {
  font-size: 22px;
  font-weight: 600;

  min-height: 20px;
}

ion-menu.md ion-list#labels-list ion-list-header {
  font-size: 16px;

  margin-bottom: 18px;

  color: #757575;

  min-height: 26px;
}

ion-menu.md ion-item {
  --padding-start: 10px;
  --padding-end: 10px;
  border-radius: 4px;
}

ion-menu.md ion-item.selected {
  --background: rgba(var(--ion-color-primary-rgb), 0.14);
}

ion-menu.md ion-item.selected ion-icon {
  color: var(--ion-color-primary);
}

ion-menu.md ion-item ion-icon {
  color: #616e7e;
}

ion-menu.md ion-item ion-label {
  font-weight: 500;
}

ion-menu.ios ion-content {
  --padding-bottom: 20px;
}

ion-menu.ios ion-list {
  padding: 20px 0 0 0;
}

ion-menu.ios ion-note {
  line-height: 24px;
  margin-bottom: 20px;
}

ion-menu.ios ion-item {
  --padding-start: 16px;
  --padding-end: 16px;
  --min-height: 50px;
}

ion-menu.ios ion-item.selected ion-icon {
  color: var(--ion-color-primary);
}

ion-menu.ios ion-item ion-icon {
  font-size: 24px;
  color: #73849a;
}

ion-menu.ios ion-list#labels-list ion-list-header {
  margin-bottom: 8px;
}

ion-menu.ios ion-list-header,
ion-menu.ios ion-note {
  padding-left: 16px;
  padding-right: 16px;
}

ion-menu.ios ion-note {
  margin-bottom: 8px;
}

ion-note {
  display: inline-block;
  font-size: 16px;

  color: var(--ion-color-medium-shade);
}

ion-item.selected {
  --color: var(--ion-color-primary);
}
</style>

The only thing that is sticking out to me is that you should only have one IonApp in your entire Vue App which is typically in App.vue. All other page/view components are wrapped in IonPage.

App.vue

<template>
    <IonApp>
        <IonRouterOutlet />
    </IonApp>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { IonApp, IonRouterOutlet } from '@ionic/vue'

export default defineComponent({
    name: 'App',
    components: {
        IonApp,
        IonRouterOutlet,
    },
})
</script>

I’m so sorry, but I did not get a notification that someone replied.

Unfortunately, I only have one IonApp element.

Below is the only other page I have

App.vue

<template>
  <ion-app>
    <ion-split-pane content-id="main-content">
      <ion-menu content-id="main-content" type="overlay">
        <ion-content>
          <ion-list id="inbox-list">
            <ion-list-header>Hello Jonathan 👋</ion-list-header>
            <ion-note>demo@tcgsniper.com</ion-note>

            <ion-menu-toggle
              auto-hide="false"
              v-for="(p, i) in appPages"
              :key="i"
            >
              <ion-item
                button
                @click="selectedIndex = i"
                router-direction="root"
                :router-link="p.url"
                lines="none"
                detail="false"
                class="hydrated"
                :class="{ selected: selectedIndex === i }"
              >
                <ion-icon
                  slot="start"
                  :ios="p.iosIcon"
                  :md="p.mdIcon"
                ></ion-icon>
                <ion-label>{{ p.title }}</ion-label>
              </ion-item>
            </ion-menu-toggle>
          </ion-list>

          <ion-list id="labels-list">
            <ion-list-header>Need help?</ion-list-header>

            <ion-item
              button
              v-for="(label, index) in helpLabels"
              @click="selectedIndex = i"
              :router-link="label.url"
              lines="none"
              :key="index"
            >
              <ion-icon
                slot="start"
                :ios="label.iosIcon"
                :md="label.mdIcon"
              ></ion-icon>
              <ion-label>{{ label.title }}</ion-label>
            </ion-item>
          </ion-list>
        </ion-content>
      </ion-menu>
      <ion-router-outlet id="main-content"></ion-router-outlet>
    </ion-split-pane>
  </ion-app>
</template>

<script lang="ts">
import {
  IonApp,
  IonContent,
  IonIcon,
  IonItem,
  IonLabel,
  IonList,
  IonListHeader,
  IonMenu,
  IonMenuToggle,
  IonNote,
  IonRouterOutlet,
  IonSplitPane,
} from "@ionic/vue";
import { defineComponent, ref } from "vue";
import { useRoute } from "vue-router";
import {
  speedometerOutline,
  speedometerSharp,
  alertOutline,
  alertSharp,
  settingsOutline,
  settingsSharp,
  archiveOutline,
  archiveSharp,
  bookmarkOutline,
  bookmarkSharp,
  heartOutline,
  heartSharp,
  mail,
  mailSharp,
  link,
  linkSharp,
  paperPlaneOutline,
  paperPlaneSharp,
  trashOutline,
  trashSharp,
  warningOutline,
  warningSharp,
} from "ionicons/icons";

export default defineComponent({
  name: "App",
  components: {
    IonApp,
    IonContent,
    IonIcon,
    IonItem,
    IonLabel,
    IonList,
    IonListHeader,
    IonMenu,
    IonMenuToggle,
    IonNote,
    IonRouterOutlet,
    IonSplitPane,
  },
  setup() {
    const selectedIndex = ref(0);
    const appPages = [
      {
        title: "Dashboard",
        url: "/dashboard",
        iosIcon: speedometerOutline,
        mdIcon: speedometerSharp,
      },
      {
        title: "Create Alert",
        url: "/alert",
        iosIcon: alertOutline,
        mdIcon: alertSharp,
      },
      {
        title: "Account Settings",
        url: "/settings",
        iosIcon: settingsOutline,
        mdIcon: settingsSharp,
      },
    ];
    const helpLabels = [
      {
        title: "How do I create a price alert?",
        url: "https://tcgsniper.com/help/how-to-create-a-price-alert-for-tcgplayer-com/",
        iosIcon: link,
        mdIcon: linkSharp,
      },
      {
        title: "help@tcgsniper.com",
        url: "mailto:help@tcgsniper",
        iosIcon: mail,
        mdIcon: mailSharp,
      },
      {
        title: "Help Center",
        url: "https://tcgsniper.com/help/",
        iosIcon: link,
        mdIcon: linkSharp,
      },
    ];

    const path = window.location.pathname.split("/")[1];
    if (path !== undefined) {
      selectedIndex.value = appPages.findIndex(
        (page) => page.title.toLowerCase() === path.toLowerCase()
      );
    }

    const route = useRoute();

    return {
      selectedIndex,
      appPages,
      helpLabels,
      archiveOutline,
      archiveSharp,
      bookmarkOutline,
      bookmarkSharp,
      heartOutline,
      heartSharp,
      mailSharp,
      paperPlaneOutline,
      paperPlaneSharp,
      trashOutline,
      trashSharp,
      warningOutline,
      warningSharp,
      isSelected: (url: string) => (url === route.path ? "selected" : ""),
    };
  },
});
</script>

<style scoped>
ion-menu ion-content {
  --background: var(--ion-item-background, var(--ion-background-color, #fff));
}

ion-menu.md ion-content {
  --padding-start: 8px;
  --padding-end: 8px;
  --padding-top: 20px;
  --padding-bottom: 20px;
}

ion-menu.md ion-list {
  padding: 20px 0;
}

ion-menu.md ion-note {
  margin-bottom: 30px;
}

ion-menu.md ion-list-header,
ion-menu.md ion-note {
  padding-left: 10px;
}

ion-menu.md ion-list#inbox-list {
  border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
}

ion-menu.md ion-list#inbox-list ion-list-header {
  font-size: 22px;
  font-weight: 600;

  min-height: 20px;
}

ion-menu.md ion-list#labels-list ion-list-header {
  font-size: 16px;

  margin-bottom: 18px;

  color: #757575;

  min-height: 26px;
}

ion-menu.md ion-item {
  --padding-start: 10px;
  --padding-end: 10px;
  border-radius: 4px;
}

ion-menu.md ion-item.selected {
  --background: rgba(var(--ion-color-primary-rgb), 0.14);
}

ion-menu.md ion-item.selected ion-icon {
  color: var(--ion-color-primary);
}

ion-menu.md ion-item ion-icon {
  color: #616e7e;
}

ion-menu.md ion-item ion-label {
  font-weight: 500;
}

ion-menu.ios ion-content {
  --padding-bottom: 20px;
}

ion-menu.ios ion-list {
  padding: 20px 0 0 0;
}

ion-menu.ios ion-note {
  line-height: 24px;
  margin-bottom: 20px;
}

ion-menu.ios ion-item {
  --padding-start: 16px;
  --padding-end: 16px;
  --min-height: 50px;
}

ion-menu.ios ion-item.selected ion-icon {
  color: var(--ion-color-primary);
}

ion-menu.ios ion-item ion-icon {
  font-size: 24px;
  color: #73849a;
}

ion-menu.ios ion-list#labels-list ion-list-header {
  margin-bottom: 8px;
}

ion-menu.ios ion-list-header,
ion-menu.ios ion-note {
  padding-left: 16px;
  padding-right: 16px;
}

ion-menu.ios ion-note {
  margin-bottom: 8px;
}

ion-note {
  display: inline-block;
  font-size: 16px;

  color: var(--ion-color-medium-shade);
}

ion-item.selected {
  --color: var(--ion-color-primary);
}
</style>

Dashboard.vue

<template>
  <ion-page>
    <ion-header :translucent="true">
      <ion-toolbar>
        <ion-buttons slot="start">
          <ion-menu-button color="primary"></ion-menu-button>
        </ion-buttons>
        <ion-title>Dashboard</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content :fullscreen="true">
      <ion-header collapse="condense">
        <ion-toolbar>
          <ion-title size="large">Dashboard</ion-title>
        </ion-toolbar>
      </ion-header>
      <PageNote text="A summary of your active and disabled alerts." />
      <ion-list>
        <ion-item>
          <ion-label>
            <h1 class="header"><span>Active</span> Alerts</h1>
          </ion-label>
          <ion-button
            class="ion-hide-sm-down"
            size="default"
            href="/alert"
            strong="true"
          >
            <ion-icon size="large" :icon="add" /> New Alert
          </ion-button>
          <ion-button
            class="ion-hide-sm-up"
            size="default"
            href="/alert"
            strong="true"
          >
            <ion-icon size="medium" :icon="add" />
          </ion-button>
        </ion-item>
      </ion-list>
      <!-- If no products exist-->
      <div v-if="products.length == 0">
        <ion-item lines="none">
          <ion-label color="medium">
            You don't have any active alerts right now.
          </ion-label>
        </ion-item>
      </div>
      <!-- EndIf no products exist-->
      <!-- Active Products -->
      <div v-for="product in products" :key="product.id">
        <ProductItem id="product.id" :product="product" :isDisabled="false" />
      </div>
      <!-- EndActive Products -->
      <ion-list>
        <ion-item>
          <ion-label>
            <h1 class="header"><span>Disabled</span> Alerts</h1>
          </ion-label>
        </ion-item>
      </ion-list>
      <div v-for="product in dProducts" :key="product.id">
        <ProductItem id="product.id" :product="product" :isDisabled="true" />
      </div>
    </ion-content>
  </ion-page>
</template>

<script>
import { defineComponent } from "vue";
import {
  IonButtons,
  IonButton,
  IonContent,
  IonHeader,
  IonMenuButton,
  IonPage,
  IonTitle,
  IonList,
  IonToolbar,
  IonIcon,
} from "@ionic/vue";

import { add, informationCircle } from "ionicons/icons";

import ProductItem from "../components/ProductItem.vue";
import PageNote from "../components/PageNote.vue";
import Product from "../models/Product";
import env from "../environment.json";
import { SniperAPI } from "../utils/AxiosInstance.js";
import { toast } from "../utils/SimpleToast.js";

export default defineComponent({
  components: {
    IonButtons,
    IonButton,
    IonContent,
    IonIcon,
    IonHeader,
    IonMenuButton,
    IonPage,
    IonTitle,
    IonList,
    IonToolbar,
    ProductItem,
    PageNote,
  },
  setup() {
    return {
      add,
    };
  },
  mounted() {
    this.initComponent();
    this.GetAlerts();
  },
  methods: {
    initComponent() {
      console.log(env.API);
      for (let index = 0; index < 0; index++) {
        this.products.push({
          id: index,
        });
      }
      for (let index = 0; index < 15; index++) {
        this.dProducts.push({
          id: index,
        });
      }
    },
    GetAlerts() {
      SniperAPI.get("dashboard/GetAlerts")
        .then(function (response) {
          if (!response.success) {
            toast("There was an error loading your alerts.");
          } else {
            toast("Success!");
          }
        })
        .catch(() => {
          toast("An unknown error occured.");
        });
    },
  },
  props: {},
  data() {
    return {
      products: [],
      dProducts: [],
    };
  },
});
</script>

<style scoped>
.header {
  font-weight: 600;
}
.header > span {
  color: var(--ion-color-primary);
}
#container {
  text-align: center;
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

#container strong {
  font-size: 20px;
  line-height: 26px;
}

#container p {
  font-size: 16px;
  line-height: 22px;
  color: #8c8c8c;
  margin: 0;
}

#container a {
  text-decoration: none;
}
</style>

You don’t have IonLabel imported in Dashboard.vue. Every used component needs to be imported in every Vue component.

Thanks a ton. This really was the issue at the end of the day. I guess I was getting confused on where the imports were missing but it turns out the first line here is usually the best hint

1 Like