Link Preview

Inline links that show a preview card on hover or keyboard focus.

Built with Motion and Tailwind CSS. Hover a link.

Edit with AIPro

Describe a change to Link Preview and get the updated code. The live preview above stays as the original.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/link-preview.tsx

    components/ui/link-preview.tsx
    "use client";
    
    import { AnimatePresence, motion } from "motion/react";
    import { ArrowUpRight } from "lucide-react";
    import { useRef, useState, type ReactNode } from "react";
    
    type LinkPreviewProps = {
      href: string;
      children: ReactNode;
      title: string;
      description: string;
      /** Two CSS colours for the preview thumbnail (swap for a real image if you have one) */
      colors?: [string, string];
    };
    
    /** Inline link that shows a preview card on hover or keyboard focus, after a short delay. */
    export function LinkPreview({ href, children, title, description, colors = ["#2563eb", "#7c3aed"] }: LinkPreviewProps) {
      const [open, setOpen] = useState(false);
      const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
      const show = () => {
        if (timer.current) clearTimeout(timer.current);
        timer.current = setTimeout(() => setOpen(true), 180);
      };
      const hide = () => {
        if (timer.current) clearTimeout(timer.current);
        timer.current = setTimeout(() => setOpen(false), 120);
      };
      let host = href;
      try {
        host = new URL(href).hostname.replace(/^www\./, "");
      } catch {}
    
      return (
        <span className="relative inline-block" onMouseEnter={show} onMouseLeave={hide}>
          <a href={href} target="_blank" rel="noreferrer" onFocus={show} onBlur={hide} className="font-medium text-zinc-900 underline decoration-zinc-300 underline-offset-4 transition-colors hover:decoration-zinc-900">
            {children}
          </a>
          <AnimatePresence>
            {open && (
              <motion.span
                role="tooltip"
                initial={{ opacity: 0, y: 8, scale: 0.96 }}
                animate={{ opacity: 1, y: 0, scale: 1 }}
                exit={{ opacity: 0, y: 4, scale: 0.98 }}
                transition={{ type: "spring", stiffness: 380, damping: 28 }}
                className="absolute bottom-full left-1/2 z-30 mb-3 block w-64 origin-bottom -translate-x-1/2 overflow-hidden rounded-2xl border border-zinc-200 bg-white text-left shadow-2xl"
              >
                <span className="relative block h-28" style={{ background: `linear-gradient(135deg, ${colors[0]}, ${colors[1]})` }}>
                  <span className="absolute inset-0 opacity-30 [background-image:radial-gradient(#fff_1px,transparent_1px)] [background-size:12px_12px]" />
                  <span className="absolute bottom-3 left-3 rounded-md bg-white/90 px-2 py-0.5 text-[11px] font-medium text-zinc-800">{host}</span>
                </span>
                <span className="block p-3.5">
                  <span className="flex items-center justify-between gap-2 text-sm font-semibold text-zinc-900">
                    {title} <ArrowUpRight className="size-3.5 shrink-0 text-zinc-400" />
                  </span>
                  <span className="mt-1 block text-xs leading-relaxed text-zinc-500">{description}</span>
                </span>
              </motion.span>
            )}
          </AnimatePresence>
        </span>
      );
    }
    
    export default function Demo() {
      return (
        <p className="max-w-md pt-40 text-center text-lg leading-relaxed text-zinc-600">
          Built with{" "}
          <LinkPreview href="https://motion.dev" title="Motion" description="A production-grade animation library for React, JavaScript and Vue.">
            Motion
          </LinkPreview>{" "}
          and{" "}
          <LinkPreview href="https://tailwindcss.com" title="Tailwind CSS" description="A utility-first CSS framework for building designs right in your markup." colors={["#06b6d4", "#3b82f6"]}>
            Tailwind CSS
          </LinkPreview>
          . Hover a link.
        </p>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.