Skip to content

Instantly share code, notes, and snippets.

@austenc
Last active February 15, 2023 10:21
Show Gist options
  • Save austenc/ff2eefcf05b660d2675453d9fcfdf431 to your computer and use it in GitHub Desktop.
Save austenc/ff2eefcf05b660d2675453d9fcfdf431 to your computer and use it in GitHub Desktop.
Alpine JS + Floating UI Tooltip Directive
import Alpine from 'alpinejs'
import tooltip from './tooltip'
Alpine.plugin(tooltip)
Alpine.start()
import { computePosition, offset, flip, arrow } from "@floating-ui/dom"
export default function (Alpine) {
Alpine.directive('tooltip', (el, { expression }, { cleanup, effect }) => {
const classes = 'opacity-0 absolute pointer-events-none z-50 block bg-gray-700 dark:bg-black rounded px-2 py-1 max-w-max text-white text-sm transition duration-300'
const arrowClasses = 'absolute w-2.5 h-2.5 rotate-45 bg-gray-700 dark:bg-black'
const tooltip = document.createElement('div')
const arrowElement = document.createElement('div')
let cleanupPosition = null
effect(() => {
tooltip.classList.add(...classes.split(' '))
tooltip.textContent = expression
el.before(tooltip)
arrowElement.classList.add(...arrowClasses.split(' '))
tooltip.appendChild(arrowElement)
})
const enter = async () => {
cleanupPosition = await computePosition(el, tooltip, {
placement: 'top',
middleware: [offset(10), flip(), arrow({ element: arrowElement })]
}).then(({ x, y, middlewareData, placement }) => {
Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px` })
tooltip.classList.toggle('opacity-0', false)
tooltip.classList.toggle('opacity-80', true)
const {x: arrowX, y: arrowY} = middlewareData.arrow;
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[placement.split('-')[0]];
Object.assign(arrowElement.style, {
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-5px',
});
})
}
const exit = () => {
tooltip.classList.toggle('opacity-80', false)
tooltip.classList.toggle('opacity-0', true)
}
el.addEventListener('mouseenter', enter)
el.addEventListener('mouseleave', exit)
cleanup(() => {
el.removeEventListener('mouseenter', enter)
el.removeEventListener('mouseleave', exit)
cleanupPosition && cleanupPosition()
})
})
}
@austenc
Copy link
Author

austenc commented Feb 14, 2023

Floating UI is a positioning engine that can help you position stuff like tooltips precisely. It is particularly useful to make sure elements are always visible. In this case, we're configuring it to flip the placement of the tooltip when it overflows the bounds of the viewport. Happy coding!

Use this directive by applying like this:

<button x-data x-tooltip="Hello, I'm a tooltip!">Hover Me</button>

Some assumptions:

  • You've already got a build system set up
  • You have installed Floating UI with npm install @floating-ui/dom
  • You're using Tailwind CSS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment