Nesting-free Tippy.js directive/component for Vue 3
There's already a popular Vue library for using Tippy.js, VueTippy, so you might (rightfully) ask, why make another one?
To put it simply, VueTippy makes a few structural concessions which I disagree with. The most significant is that when
using VueTippy, adding a complex (i.e. non-directive) tooltip to an element will wrap it in a <span>
, which can easily
screw up layout styles. On top of that, I find their syntax clunky and ugly. Wrapping every tooltipped component in a
slot seems unnecessary.
A clumsy tool will wind up chronically underutilized, so Tippy.vue has been designed from the start with a strong focus on ergonomics. Adding a tooltip is a simple, drop-in addition, with no structural or styling changes necessary.
<!-- VueTippy -->
<tippy>
<button>Tippy!</button>
<template #content>
Hi!
</template>
</tippy>
<!-- Tippy.vue -->
<button v-tippy>Tippy!</button>
<tippy>Hi!</tippy>
# npm
npm i tippy.vue
# Yarn
yarn add tippy.vue
Tippy.vue doesn't bundle Tippy.js. The most up-to-date Tippy install process is explained in the Tippy docs, but as of the time of writing, these are the necessary scripts:
<!-- Development -->
<script src="https://unpkg.com/@popperjs/core@2/dist/umd/popper.min.js"></script>
<script src="https://unpkg.com/tippy.js@6/dist/tippy-bundle.umd.js"></script>
<script src="https://unpkg.com/tippy.vue@3"></script>
<!-- Production -->
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script src="https://unpkg.com/tippy.vue@3"></script>
You can use Tippy.vue as a plugin or access the individual components directly:
// use the plugin
import {TippyPlugin} from 'tippy.vue';
app.use(TippyPlugin);
app.use(TippyPlugin, {
// convenience to set tippy.js default props
});
// or add them individually
import {TippyDirective, Tippy, TippySingleton} from 'tippy.vue';
app.directive('tippy', TippyDirective);
app.component('tippy', Tippy);
app.component('tippy-singleton', TippySingleton);
import tippy from 'tippy.js'
tippy.setDefaultProps({
// default tippy props
});
/* add styles/themes to your global stylesheet */
@import '~tippy.js/dist/tippy.css';
You can also add them in individual components:
<template>
<div>
<div v-tippy>Wow</div>
<tippy>Cool</tippy>
</div>
</template>
<script>
import {Tippy, TippyDirective} from 'tippy.vue'
export default {
components: {
Tippy
},
directives: {
tippy: TippyDirective
}
}
</script>
// use the plugin
app.use(TippyVue);
app.use(TippyVue, {
// convenience to set tippy.js default props
});
// or add them individually
app.directive('tippy', TippyVue.TippyDirective);
app.component('tippy', TippyVue.Tippy);
app.component('tippy-singleton', TippyVue.TippySingleton);
tippy.setDefaultProps({
// default tippy props
});
<button v-tippy="'Some content'">Static</button>
<button v-tippy="`Seconds: ${seconds}`">Counter</button>
<button v-tippy="{content: 'Some content', position: 'right'}">Side</button>
<!-- Target arbitrary elements -->
<button v-tippy>Basic</button>
<tippy>Time: <i>{{seconds}}</i></tippy>
<!-- Named targets -->
<button v-tippy:fancy>Fancy</button>
<tippy target="fancy">Fancy time: <i>{{seconds}}</i></tippy>
<!-- Target the parent -->
<button>
Button
<tippy target="_parent">Bound to parent</tippy>
</button>
<!-- Commonly used options have dedicated props -->
<button v-tippy></button>
<tippy placement="right">On the right</tippy>
<!-- More niche options can be passed into the 'extra' prop -->
<button v-tippy>Follow me</button>
<tippy :extra="{followCursor: true}">Tip</tippy>
<tippy-singleton move-transition="transform 0.1s ease-out"/>
<button v-tippy>1</button>
<tippy singleton>Item 1</tippy>
<button v-tippy>2</button>
<tippy singleton>Item 2</tippy>
<button v-tippy>3</button>
<tippy singleton>Item 3</tippy>
Setting up an environment is very standard. Make sure you're on the latest version of yarn, then run:
yarn install
yarn docs:dev
Tippy.vue doesn't have a vue property for every Tippy.js prop, instead providing extra
for additional options. This
is by design, since it keeps the API clean and easy to understand. I am however open to suggestions for other options
that should be exposed directly in Vue, however you'll have to convince me they'll be useful enough to warrant the
increase in API complexity.
In the future I intend to implement a sort of meta-plugin system that would allow user-defined shorthand props.