Docs
Go back
Component by
OV
Oblivionhover me
TSX
glass-tooltip
"use client"
import React, { useState, useRef } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { cn } from "@/lib/utils"
export interface GlassTooltipProps {
children?: React.ReactNode
label?: React.ReactNode
position?: "top" | "bottom" | "left" | "right"
delay?: number
className?: string
contentClassName?: string
}
export const componentMeta = {
name: "Glass Tooltip",
description: "An aesthetic, physics-based glassmorphism tooltip with subtle borders and fluid transitions.",
props: {
label: { type: "ReactNode", description: "The content inside the tooltip", default: '"Majestic Tooltip"' },
position: { type: "enum", description: "Where the tooltip appears", default: '"top"' },
delay: { type: "number", description: "Show delay in ms", default: "0" },
children: { type: "ReactNode", description: "The trigger element" }
}
}
// ─── Internal Pure Component ──────────────────────────────────────────────
function TooltipCore({ children, label, position = "top", delay = 0, contentClassName }: GlassTooltipProps) {
const [isVisible, setIsVisible] = useState(false)
const timerRef = useRef<NodeJS.Timeout | null>(null)
const handleMouseEnter = () => {
timerRef.current = setTimeout(() => setIsVisible(true), delay)
}
const handleMouseLeave = () => {
if (timerRef.current) clearTimeout(timerRef.current)
setIsVisible(false)
}
const variants = {
initial: {
opacity: 0,
scale: 0.92,
y: position === "top" ? 8 : position === "bottom" ? -8 : 0,
x: position === "left" ? 8 : position === "right" ? -8 : 0,
},
animate: {
opacity: 1,
scale: 1,
y: 0,
x: 0,
transition: { type: "spring" as const, stiffness: 450, damping: 25 }
},
exit: {
opacity: 0,
scale: 0.95,
y: position === "top" ? 4 : position === "bottom" ? -4 : 0,
x: position === "left" ? 4 : position === "right" ? -4 : 0,
transition: { duration: 0.1 }
}
}
const posClasses = {
top: "bottom-full left-1/2 -translate-x-1/2 mb-3",
bottom: "top-full left-1/2 -translate-x-1/2 mt-3",
left: "right-full top-1/2 -translate-y-1/2 mr-3",
right: "left-full top-1/2 -translate-y-1/2 ml-3"
}
const arrowClasses = {
top: "top-full left-1/2 -translate-x-1/2 border-t-white/80 dark:border-t-zinc-800 border-x-transparent border-b-transparent",
bottom: "bottom-full left-1/2 -translate-x-1/2 border-b-white/80 dark:border-b-zinc-800 border-x-transparent border-t-transparent",
left: "left-full top-1/2 -translate-y-1/2 border-l-white/80 dark:border-l-zinc-800 border-y-transparent border-r-transparent",
right: "right-full top-1/2 -translate-y-1/2 border-r-white/80 dark:border-r-zinc-800 border-y-transparent border-l-transparent"
}
return (
<div
className="relative inline-block"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
<AnimatePresence>
{isVisible && (
<motion.div
initial="initial" animate="animate" exit="exit" variants={variants}
className={cn("absolute z-50 pointer-events-none select-none", posClasses[position as keyof typeof posClasses])}
>
<div className={cn(
"relative px-3 py-1.5 rounded-xl text-xs font-medium tracking-tight text-neutral-900 dark:text-neutral-100 shadow-2xl overflow-hidden min-w-[max-content]",
"bg-white/80 dark:bg-zinc-800/80 backdrop-blur-xl border border-white/20 dark:border-white/10",
contentClassName
)}>
<div className="relative z-10 antialiased flex items-center gap-2">
{label || "Tooltip"}
</div>
</div>
<div className={cn("absolute w-0 h-0 border-4", arrowClasses[position as keyof typeof arrowClasses])} />
</motion.div>
)}
</AnimatePresence>
</div>
)
}
// ─── Main Export ──────────────────────────────────────────────────────────
export default function GlassTooltip(props: GlassTooltipProps) {
if (!props.children) {
return (
<div className="flex flex-col items-center justify-center min-h-[500px] w-full bg-slate-50 dark:bg-[#000] rounded-[2rem] p-12 overflow-hidden relative border border-black/5 dark:border-white/5 font-sans group/stage">
<TooltipCore position="top" label={
<div className="font-medium px-2 py-0.5 max-w-[200px] leading-snug">
"The first rule of Fight Club is: you do not talk about Fight Club."
</div>
}>
<motion.div
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="px-10 py-5 rounded-full bg-white dark:bg-zinc-900 text-neutral-800 dark:text-neutral-200 font-bold text-lg cursor-default border border-black/5 dark:border-white/10 shadow-xl transition-all hover:bg-neutral-50 dark:hover:bg-zinc-800"
>
hover me
</motion.div>
</TooltipCore>
</div>
)
}
return <TooltipCore {...props} />
}"use client"
import React, { useState, useRef } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { cn } from "@/lib/utils"
export interface GlassTooltipProps {
children?: React.ReactNode
label?: React.ReactNode
position?: "top" | "bottom" | "left" | "right"
delay?: number
className?: string
contentClassName?: string
}
export const componentMeta = {
name: "Glass Tooltip",
description: "An aesthetic, physics-based glassmorphism tooltip with subtle borders and fluid transitions.",
props: {
label: { type: "ReactNode", description: "The content inside the tooltip", default: '"Majestic Tooltip"' },
position: { type: "enum", description: "Where the tooltip appears", default: '"top"' },
delay: { type: "number", description: "Show delay in ms", default: "0" },
children: { type: "ReactNode", description: "The trigger element" }
}
}
// ─── Internal Pure Component ──────────────────────────────────────────────
function TooltipCore({ children, label, position = "top", delay = 0, contentClassName }: GlassTooltipProps) {
const [isVisible, setIsVisible] = useState(false)
const timerRef = useRef<NodeJS.Timeout | null>(null)
const handleMouseEnter = () => {
timerRef.current = setTimeout(() => setIsVisible(true), delay)
}
const handleMouseLeave = () => {
if (timerRef.current) clearTimeout(timerRef.current)
setIsVisible(false)
}
const variants = {
initial: {
opacity: 0,
scale: 0.92,
y: position === "top" ? 8 : position === "bottom" ? -8 : 0,
x: position === "left" ? 8 : position === "right" ? -8 : 0,
},
animate: {
opacity: 1,
scale: 1,
y: 0,
x: 0,
transition: { type: "spring" as const, stiffness: 450, damping: 25 }
},
exit: {
opacity: 0,
scale: 0.95,
y: position === "top" ? 4 : position === "bottom" ? -4 : 0,
x: position === "left" ? 4 : position === "right" ? -4 : 0,
transition: { duration: 0.1 }
}
}
const posClasses = {
top: "bottom-full left-1/2 -translate-x-1/2 mb-3",
bottom: "top-full left-1/2 -translate-x-1/2 mt-3",
left: "right-full top-1/2 -translate-y-1/2 mr-3",
right: "left-full top-1/2 -translate-y-1/2 ml-3"
}
const arrowClasses = {
top: "top-full left-1/2 -translate-x-1/2 border-t-white/80 dark:border-t-zinc-800 border-x-transparent border-b-transparent",
bottom: "bottom-full left-1/2 -translate-x-1/2 border-b-white/80 dark:border-b-zinc-800 border-x-transparent border-t-transparent",
left: "left-full top-1/2 -translate-y-1/2 border-l-white/80 dark:border-l-zinc-800 border-y-transparent border-r-transparent",
right: "right-full top-1/2 -translate-y-1/2 border-r-white/80 dark:border-r-zinc-800 border-y-transparent border-l-transparent"
}
return (
<div
className="relative inline-block"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
<AnimatePresence>
{isVisible && (
<motion.div
initial="initial" animate="animate" exit="exit" variants={variants}
className={cn("absolute z-50 pointer-events-none select-none", posClasses[position as keyof typeof posClasses])}
>
<div className={cn(
"relative px-3 py-1.5 rounded-xl text-xs font-medium tracking-tight text-neutral-900 dark:text-neutral-100 shadow-2xl overflow-hidden min-w-[max-content]",
"bg-white/80 dark:bg-zinc-800/80 backdrop-blur-xl border border-white/20 dark:border-white/10",
contentClassName
)}>
<div className="relative z-10 antialiased flex items-center gap-2">
{label || "Tooltip"}
</div>
</div>
<div className={cn("absolute w-0 h-0 border-4", arrowClasses[position as keyof typeof arrowClasses])} />
</motion.div>
)}
</AnimatePresence>
</div>
)
}
// ─── Main Export ──────────────────────────────────────────────────────────
export default function GlassTooltip(props: GlassTooltipProps) {
if (!props.children) {
return (
<div className="flex flex-col items-center justify-center min-h-[500px] w-full bg-slate-50 dark:bg-[#000] rounded-[2rem] p-12 overflow-hidden relative border border-black/5 dark:border-white/5 font-sans group/stage">
<TooltipCore position="top" label={
<div className="font-medium px-2 py-0.5 max-w-[200px] leading-snug">
"The first rule of Fight Club is: you do not talk about Fight Club."
</div>
}>
<motion.div
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="px-10 py-5 rounded-full bg-white dark:bg-zinc-900 text-neutral-800 dark:text-neutral-200 font-bold text-lg cursor-default border border-black/5 dark:border-white/10 shadow-xl transition-all hover:bg-neutral-50 dark:hover:bg-zinc-800"
>
hover me
</motion.div>
</TooltipCore>
</div>
)
}
return <TooltipCore {...props} />
}Save to favoritesCopy to Figma
React
Glass Tooltip
An aesthetic, physics-based glassmorphism tooltip with subtle borders and fluid transitions.
Installation
$ npx @cosmoo/oblivion add glass-tooltipDependencies
npm install framer-motionUsage Example
tsx
import GlassTooltip from "@/components/oblivion/glass-tooltip"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<GlassTooltip />
</div>
)
}import GlassTooltip from "@/components/oblivion/glass-tooltip"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<GlassTooltip />
</div>
)
}