Search code examples
vue.jsvuetify.jstooltipv-tooltip

Vuetify - v-tooltip-Not displaying with keyboard tap button


I have a shared component using Vuetify's v-tooltip, and it works perfectly when hovering with the mouse. However, when I use the tap button to access a button that has the tooltip, it doesn't work. Can someone help me troubleshoot this issue?

Here's the code for my tooltip component:

<template>
  <v-tooltip
    v-bind="$attrs"
    :open-on-focus="true"
    :content-class="`${tooltipClass} custom-tooltip ${color} darken-2`"
    open-delay="250"
    max-width="288px"
  >
    <template v-slot:activator="{ on, attrs }">
      <div v-bind="attrs" v-on="on">
        <slot />
      </div>
    </template>
    <span class="tooltip-text">{{ getTooltipText() }}</span>
  </v-tooltip>
</template>

And here's how I use it:

<tooltip
  tooltip-text="edit metadata"
  bottom
  position="bottom"
>
  <EditExternalButton
    :external-link="item.editLink"
    :data-test="`result-list-item-edit-external-${i + 1}`"
    event-name-string="metadata_edit clicked"
  />
</tooltip>

I tried to add :open-on-focus="true"

in props but it didn't work


Solution

  • A regular div usually cannot be focused, so you'll never reach it through the keyboard and the handler won't be triggered.

    There are several ways around it:

    • add tabindex="0" to the div to make it tabbable:
    <template v-slot:activator="{ on, attrs }">
      <div v-bind="attrs" v-on="on" tabindex="0">
        <slot />
      </div>
    </template>
    

    const tooltip = {
      template: `
        <v-tooltip
          v-bind="$attrs"
          :open-on-focus="true"
          open-delay="250"
          max-width="288px"
        >
          <template v-slot:activator="{ on, attrs }">
            <div v-bind="attrs" v-on="on" tabindex="0">
              <slot />
            </div>
          </template>
          <span class="tooltip-text">getTooltipText()</span>
        </v-tooltip>
      `
    }
    
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      components: {tooltip},
      template: `
        <v-app>
          <v-main>
            <v-container>
              <tooltip>
                <div style="background: pink;">activator content</div>
              </tooltip>
            
            </v-container>
          </v-main>
        </v-app>
      `,
      setup(){
      
      }
    })
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
    
    <div id="app"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>

    • use a natively tabbable element (like button) instead of a div as activator:
    <template v-slot:activator="{ on, attrs }">
      <button v-bind="attrs" v-on="on">
        <slot />
      </button>
    </template>
    

    const tooltip = {
      template: `
        <v-tooltip
          v-bind="$attrs"
          :open-on-focus="true"
          open-delay="250"
          max-width="288px"
        >
          <template v-slot:activator="{ on, attrs }">
            <v-btn v-bind="attrs" v-on="on">
              <slot />
            </v-btn>
          </template>
          <span class="tooltip-text">getTooltipText()</span>
        </v-tooltip>
      `
    }
    
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      components: {tooltip},
      template: `
        <v-app>
          <v-main>
            <v-container>
              <tooltip>
                <div style="background: pink;">activator content</div>
              </tooltip>
            
            </v-container>
          </v-main>
        </v-app>
      `,
      setup(){
      
      }
    })
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
    
    <div id="app"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>

    • pass the focus-related handlers on to the slot content and use it there:
    <template v-slot:activator="{ attrs, on: {mouseenter, mouseleave, focus, blur, keydown} }">
      <div v-bind="attrs" v-on="{mouseenter, mouseleave}">
        <slot :on="{focus, blur, keydown}"/>
      </div>
    </template>
    

    const tooltip = {
      template: `
        <v-tooltip
          v-bind="$attrs"
          :open-on-focus="true"
          open-delay="250"
          max-width="288px"
        >
          <template v-slot:activator="{ attrs, on: {mouseenter, mouseleave, focus, blur, keydown} }">
            <div v-bind="attrs" v-on="{mouseenter, mouseleave}">
              <slot :on="{focus, blur, keydown}"/>
            </div>
          </template>
          <span class="tooltip-text">getTooltipText()</span>
        </v-tooltip>
      `
    }
    
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      components: {tooltip},
      template: `
        <v-app>
          <v-main>
            <v-container>
    
              <tooltip v-slot="{on}">
                <v-checkbox v-on="on"/>
              </tooltip>
          
            </v-container>
          </v-main>
        </v-app>
      `,
      setup(){
      
      }
    })
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
    
    <div id="app"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>