80

I have the following jsfiddle that has two Vuetify tabs. The documentation doesn't show examples on using vue-router with them.

I found this Medium.com post on how to use Vuetify with vue-router, which has the following code:

<div id="app">
  <v-tabs grow light>
    <v-tabs-bar>
      <v-tabs-item href="/" router>
        <v-icon>motorcycle</v-icon>
      </v-tabs-item>
      <v-tabs-item href="/dog" router>
        <v-icon>pets</v-icon>
      </v-tabs-item>
    </v-tabs-bar>
  </v-tabs>

  <router-view />
</div>

However, the code is now outdated as the Vuetify 1.0.13 Tabs documentation doesn't specify a router prop in their api, so the embedded example in the post doesn't work.

I also found this StackOverflow answer which had the following code:

<v-tabs-item :to="{path:'/path/to/somewhere'}">

However, using the to prop doesn't work and it's also not listed in the Vuetify api. In contrast, the v-button Vuetify component does list a to prop and utilizes vue-router, so I would expect a vue-router supported component to support the to prop.

Digging around in the old old Vuetify 0.17 docs, the to prop is specified for v-tabs-item. It seems that support might have been removed in 1.0.13.

How can I use vue-router with Vuetify tabs?

homersimpson
  • 4,124
  • 4
  • 29
  • 39

8 Answers8

97

Update

Holy wow! I asked the Vuetify community to add documentation to their api, and it looks like they just added the to prop as well as other vue-router props to the Vuetify tabs docs. Seriously, the community there is awesome.

Original Answer

The folks in the Vuetify community Discord were able to help me out. My updated jsfiddle now has the working code.

Essentially, v-tab is a wrapper for router-link, where I assume it uses slots to pass props to router-link, so putting the to prop on v-tab works fine.

The following code is an example of the working code:

html

<v-app dark>
  <v-tabs fixed-tabs>
    <v-tab to="/foo">Foo</v-tab>
    <v-tab to="/bar">Bar</v-tab>
  </v-tabs>
  <router-view></router-view>
</v-app>

js

const Foo = {
  template: '<div>Foo component!</div>'
};

const Bar = {
  template: '<div>Bar component!</div>'
};

const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar },
];

const router = new VueRouter({ routes });

new Vue({
  el: '#app',
  router,
});

Result

Foo tab example Bar tab example

homersimpson
  • 4,124
  • 4
  • 29
  • 39
27

The template part:

<div>
    <v-tabs
      class="tabs"
      centered
      grow
      height="60px"
      v-model="activeTab"
    >
      <v-tab v-for="tab in tabs" :key="tab.id" :to="tab.route" exact>
        {{ tab.name }}
      </v-tab>
    </v-tabs>
    <router-view></router-view>
</div>

And the js part:

  data() {
    return {
      activeTab: `/user/${this.id}`,
      tabs: [
        { id: 1, name: "Task", route: `/user/${this.id}` },
        { id: 2, name: "Project", route: `/user/${this.id}/project` }
      ]
    };
  }

Routes:

{
  path: "/user/:id",
  component: User1,
  props: true,
  children: [
    {
      path: "", //selected tab by default
      component: TaskTab
    },
    {
      path: "project",
      component: ProjectTab
    }
  ]
}

See codesanbox example

Roland
  • 24,554
  • 4
  • 99
  • 97
  • The problem I see is when trying to do this, it ends up loading each page twice. If you watch for `mounted()` you'll see that it loads the page twice when using tabs for routing. Need to find a fix for this. https://codesandbox.io/s/vuetify-v-tabs-with-vue-router-in1le watch console for "build loaded" should only do once, but it actually mounts twice. – tmarois Jun 17 '19 at 23:31
  • I figured it out. noticed its the route-view loop you're running. You shouldn't run the route-view in the tab array. Causes it to load twice. – tmarois Jun 17 '19 at 23:48
  • 2
    @timothymarois you are absolutely correct. I updated my answer and now it should work as expected. – Roland Jun 18 '19 at 06:07
  • what if the component needs props from the parent? – George Katsanos Oct 11 '19 at 16:14
  • why do you need props ? If the props have to do with router, you can pass props through router. – Roland Oct 11 '19 at 16:20
16

I'm just adding some animation-related tips here & clarifying the use of v-tab-items vs v-tab-item.

If you have noticed, using the working markup as follows prevents the v-tabs tab switch animation to work:

<v-tabs v-model="activeTab">
  <v-tab key="tab-1" to="/tab-url-1">tab 1</v-tab>
  <v-tab key="tab-2" to="/tab-url-2">tab 2</v-tab>
</v-tabs>
<router-view />

If you want to keep the tab switch animation, this is a way to do it.

<v-tabs v-model="activeTab">
  <v-tab key="tab-1" to="/tab-url-1" ripple>
    tab 1
  </v-tab>
  <v-tab key="tab-2" to="/tab-url-2" ripple>
    tab 2
  </v-tab>

  <v-tab-item id="/tab-url-1">
    <router-view v-if="activeTab === '/tab-url-1'" />
  </v-tab-item>
  <v-tab-item id="/tab-url-2">
    <router-view v-if="activeTab === '/tab-url-2'" />
  </v-tab-item>
</v-tabs>

Note that you can also use a v-for loop on your v-tab and v-tab-item tags as long as your to value is found among the id attribute of your v-tab-items.

If you need to place your tab contents in a different place than your tabs buttons, this is what v-tab-items is for. You can place the v-tab-items in a v-tab-items tag outside of the v-tabs component. Make sure you give it a v-model="activeTab" attribute.

antoni
  • 5,001
  • 1
  • 35
  • 44
3

With animation and swipe enabled

And keep your existing query params: usefull e.g. if you have a :locale in your url:

Template

<v-tabs v-model="activeTab">
   <v-tab v-for="tab in tabs" :key="tab.id" :to="tab.to">
    {{ tab.name }}
  </v-tab>
</v-tabs>

<v-tabs-items v-model="activeTab" @change="updateRouter($event)">
  <v-tab-item v-for="tab in tabs" :key="tab.id" :value="tab.to">
    <router-view />          
  </v-tab-item>
</v-tabs-items>

Script

export default {
  data: () => ({
    activeTab: '',
  }),
  computed: {
    tabs() {
      return [
        { id: 1, name: 'Tab one', to: this.getTabPath('routeName1') },
        { id: 2, name: 'Tab two', to: this.getTabPath('routeName2') },
        { id: 3, name: 'Tab three', to: this.getTabPath('routeName3') },
        { id: 4, name: 'Tab four', to: this.getTabPath('routeName4') },
      ]
    },
  },
  methods: {
    getTabPath(name) {
      // Get the path without losing params. Usefull e.g. if you have a :locale in your url:
      return this.$router.resolve({ name, params: this.$route.params }).href
    },
    updateRouter(path) {
      // Gets called upon swipe.
      this.$router.push(path)
    },
  },
}

Using $router.resolve to get the path from a route object as explained here.

Raymundus
  • 2,173
  • 1
  • 20
  • 35
2

The answers of @roli-roli and @antoni are currect but lacking of a tiny detail. In fact, using their methods (almost equivalent) there is an issue in mobile views; in fact, in such conditions tabs become swipeable. The problem is that swiping won't update the route as expected, passing from Tab A with component A to Tab B with component B; instead, an animation will be fired and the activeTab will change, but the router won't update.

TL;DR Swiping the v-tab-item does not update the router, and you get the same component under each tab.

Solutions could be either disable the swipeability of v-tab-item or listening to the change event of the tab component to update the router accordingly. I would advise this second solution (since swiping results pretty handy in some conditions), but I thik that this would trigger twice the router update when clicking on the tab's label.

Here's a working example based on @roli-roli answer

Template

<template>
    <v-tabs v-model="activeTab">
        <v-tab v-for="tab in tabs" :key="tab.id" :to="tab.route">{{ tab.name }}</v-tab>

        <v-tabs-items v-model="activeTab" @change="updateRouter($event)">
            <v-tab-item v-for="tab in tabs" :key="tab.id" :to="tab.route">
                <router-view />          
            </v-tab-item>
        </v-tabs-items>
    </v-tabs>
</template>

Script

export default {
    data: () => ({
        activeTab: '',
        tabs: [
            {id: '1', name: 'Tab A', route: 'component-a'},
            {id: '2', name: 'Tab B', route: 'component-b'}
        ]
    }),
    methods: {
        updateRouter(val){
            this.$router.push(val)
        }
    }
}

Router

Set up as in previous answers.

  • 1
    thanks! use :value="tab.route" instead of :to and – Stevie-Ray Hartog May 24 '19 at 00:21
  • @Stevie-RayHartog yep, I corrected that miswritten router-view. Why would you suggest to use :value instead of :to? – Gabriel Rambaud May 25 '19 at 09:29
  • The problem I see is when trying to do this, it ends up loading each page twice. If you watch for `mounted()` you'll see that it loads the page twice when using tabs for routing. Need to find a fix for this. https://codesandbox.io/s/vuetify-v-tabs-with-vue-router-in1le watch console for "build loaded" should only do once, but it actually mounts twice. – tmarois Jun 17 '19 at 23:32
  • I figured it out. noticed its the route-view loop you're running. You shouldn't run the route-view in the tab array. Causes it to load twice. – tmarois Jun 17 '19 at 23:48
  • To disable tabs swiping, simply use the `touchless` attribute like this `` – rustyx Nov 28 '19 at 12:57
  • I was having problems doing this using an object for the "to" value, rather than a string. It seems vuetify will resolve the route to a string, and set the v-model to that, so resolving it to a string for the v-tab-item value will allow it to be referenced correctly. https://codesandbox.io/s/v-tab-to-7jteu – discomatt Mar 30 '20 at 01:44
  • You should avoid using arrow functions for 'data' - something like this.$route will break the code because 'this' is set to global in array functions not to the vue instance. – Hexodus Aug 20 '20 at 20:06
  • I don't know why, but for me it works only if I change "to" with "value" on the tab items – EuberDeveloper May 14 '21 at 09:12
0
//urls in some componet
<v-btn @click="goToUrl({name: 'RouteName1'})">
    .....
<v-list-item-title 
    @click="goToUrl({name: 'RouteName2'})"
>
    Some tab link text
 </v-list-item-title>
    ....


//tabs menu and content in other component
<v-tabs>
    <v-tab key="key_name1" :to="{ name: 'RouteName1'}">Some link 1</v-tab>
    <v-tab key="key_name2" :to="{ name: 'RouteName2'}">Some link 2</v-tab>

<v-tab-item key="key_name1" value="/url/for/route1">
    ....
<v-tab-item key="key_name2" value="/url/for/route2">
    ....
B Ii
  • 49
  • 3
0

I just want to add a fix for double mounting of the new component when a tab is switched to. You can see the question and answer here.

The TLDR is that if you use a v-for within the , you are going to run into a problem where the component you are switching to will be created, destroyed and then created again on each switch. If, like me, you do an ajax call on the create side of that, then you will be hitting your back end twice for every tab switch. You can wrap the with a to prevent that.

in the answer I go into depth about why this happens.

Greg Veres
  • 1,770
  • 19
  • 28
-1

The following code works for me

  <v-tabs fixed-tabs>
          <v-tab>Locations</v-tab>
          <v-tab>Employees</v-tab>
          <v-tab-item>Tab 1 content
            <locations-data/>
          </v-tab-item>
          <v-tab-item>Tab 2 content
            <employees-data/>
          </v-tab-item>
    </v-tabs>
   <script>
        import LocationsData from './Settings/Locations';
        import EmployeesData from './Settings/Employees';
        export default {
           components: {
            LocationsData,
            EmployeesData
          },
        }
   </script>
  • for first time i got stretched line below the tab for first time how to resolve this issue . – Atchutha rama reddy Karri Aug 01 '19 at 03:58
  • I've done your way in the past and if I remember well, it loads every sub-component when your primary one is mounted. Whereas playing with `:to` or `to` props load the corresponding component only when the tab is clicked. Which is better in my opinion. – lbris Mar 26 '20 at 09:29