vue.jsvuetify.jsvuetifyjs3

How to update tab slider manually?


I am trying to combine tabs and menus for navigation. I placed two tabs and one menu in the header, and configured vue router for them. When I click the tabs, the tab slider (the white line under the selected tab) switches correctly, but when I click the menu items, the slider doesn't move. How to manually update the slider?

Minimal reproducible example

This is based on the "Overflow to menu" example on the official website.

CodeSandbox link

src/App.vue:

<template>
  <v-app>
    <v-tabs>
      <v-tab to="/1">Tab 1</v-tab>
      <v-tab to="/2">Tab 2</v-tab>
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn v-bind="props">Tab 3</v-btn>
        </template>
        <v-list>
          <v-list-item to="/3?item=1">Item 1</v-list-item>
          <v-list-item to="/3?item=2">Item 2</v-list-item>
        </v-list>
      </v-menu>
    </v-tabs>
    <v-main>
      <router-view />
    </v-main>
  </v-app>
</template>

src/pages/1.vue:

<template>
  /1
</template>

src/pages/2.vue:

<template>
  /2
</template>

src/pages/3.vue:

<template>
  /3 (item {{ route.query.item }})
</template>

<script lang="ts" setup>
  import { useRoute } from 'vue-router';
  const route = useRoute();
</script>

Screenshot: Item 1 in Tab 3 is selected, however, the tab slider is still under Tab 2.

Item 1 in Tab 3 is selected, however, the tab slider is still under Tab 2


Solution

  • After a lot of hard work, I finally came up with a perfect yet simple solution:

    CodeSandbox Link

    New App.vue:

    <template>
      <v-app>
        <v-tabs>
          <v-tab to="/1">Tab 1</v-tab>
          <v-tab to="/2">Tab 2</v-tab>
          <v-menu>
            <template v-slot:activator="{ props }">
              <v-tab v-bind="props" append-icon="mdi-menu-down">Tab 3</v-tab>
            </template>
            <v-list>
              <v-list-item to="/3?item=1" exact>Item 1</v-list-item>
              <v-list-item to="/3?item=2" exact>Item 2</v-list-item>
            </v-list>
          </v-menu>
        </v-tabs>
        <v-main>
          <router-view />
        </v-main>
      </v-app>
    </template>
    
    <script lang="ts" setup>
    import { computed } from 'vue';
    import { useRoute } from 'vue-router';
    
    const route = useRoute();
    const selectedTab = computed({
      get() {
        switch (route.path) {
          case '/1':
            return 0;
          case '/2':
            return 1;
          case '/3':
            return 2;
          default:
            return 0;
        }
      },
      set() {
        // Do nothing.
      }
    });
    </script>
    

    Instead of intercepting the update logic of the <v-tabs> component, I use a writable computed to rewrite the update logic and make it solely dependent on the current route.

    If you can read Chinese, you can take a look at my blog, which covers more details.