Web InnoventixFreeCode

Link Slide Hover Effect

Original · free

Links whose label slides up to a duplicate on hover.

byWeb InnoventixReact + Tailwind
hoverlinkslide
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/hover-link-slide.json
hover-link-slide.tsx
"use client";

type LinkItem = {
  label: string;
  href: string;
};

const navLinks: LinkItem[] = [
  { label: "Work", href: "#work" },
  { label: "Studio", href: "#studio" },
  { label: "Journal", href: "#journal" },
  { label: "Contact", href: "#contact" },
];

const destinations: LinkItem[] = [
  { label: "Read the 2026 State of Interface report", href: "#report" },
  { label: "Browse the open-source component library", href: "#library" },
  { label: "Subscribe to the Friday design dispatch", href: "#dispatch" },
  { label: "See which roles the studio is hiring for", href: "#careers" },
];

function SlideLink({
  href,
  label,
  accent,
}: {
  href: string;
  label: string;
  accent: string;
}) {
  return (
    <a
      href={href}
      className="group relative inline-block rounded-sm text-base font-medium tracking-tight text-slate-800 outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-4 focus-visible:ring-offset-slate-50 dark:text-slate-100 dark:focus-visible:ring-offset-slate-950 sm:text-lg"
    >
      <span className="relative block overflow-hidden">
        <span className="hls-slide block transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:-translate-y-full group-focus-visible:-translate-y-full">
          {label}
        </span>
        <span
          aria-hidden="true"
          className={`hls-slide absolute inset-0 block translate-y-full transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:translate-y-0 group-focus-visible:translate-y-0 ${accent}`}
        >
          {label}
        </span>
      </span>
    </a>
  );
}

function ArrowUpRight() {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      className="h-full w-full"
      aria-hidden="true"
    >
      <path d="M7 17 17 7" />
      <path d="M8 7h9v9" />
    </svg>
  );
}

function DestinationRow({ label, href }: LinkItem) {
  return (
    <a
      href={href}
      className="group relative flex items-center justify-between gap-4 rounded-sm border-t border-slate-200 py-5 outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-4 focus-visible:ring-offset-slate-50 dark:border-slate-800 dark:focus-visible:ring-offset-slate-950"
    >
      <span className="relative block overflow-hidden text-lg font-medium tracking-tight text-slate-900 dark:text-slate-100 sm:text-xl">
        <span className="hls-slide block transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:-translate-y-full group-focus-visible:-translate-y-full">
          {label}
        </span>
        <span
          aria-hidden="true"
          className="hls-slide absolute inset-0 block translate-y-full text-indigo-600 transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:translate-y-0 group-focus-visible:translate-y-0 dark:text-indigo-400"
        >
          {label}
        </span>
      </span>

      <span className="relative block h-5 w-5 shrink-0 overflow-hidden text-slate-400 group-hover:text-indigo-600 group-focus-visible:text-indigo-600 dark:text-slate-500 dark:group-hover:text-indigo-400 dark:group-focus-visible:text-indigo-400">
        <span className="hls-arrow absolute inset-0 block transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:-translate-y-full group-hover:translate-x-full group-focus-visible:-translate-y-full group-focus-visible:translate-x-full">
          <ArrowUpRight />
        </span>
        <span
          aria-hidden="true"
          className="hls-arrow absolute inset-0 block -translate-x-full translate-y-full transition-transform duration-500 ease-[cubic-bezier(0.65,0,0.35,1)] group-hover:translate-x-0 group-hover:translate-y-0 group-focus-visible:translate-x-0 group-focus-visible:translate-y-0"
        >
          <ArrowUpRight />
        </span>
      </span>
    </a>
  );
}

export default function HoverLinkSlide() {
  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-24 text-slate-900 dark:bg-slate-950 dark:text-slate-100 sm:px-10 sm:py-32">
      <style>{`
        @keyframes hls-fade-rise {
          from { opacity: 0; transform: translateY(10px); }
          to { opacity: 1; transform: none; }
        }
        .hls-reveal { animation: hls-fade-rise 0.6s cubic-bezier(0.65, 0, 0.35, 1) both; }
        @media (prefers-reduced-motion: reduce) {
          .hls-slide, .hls-arrow, .hls-reveal {
            transition: none !important;
            animation: none !important;
            transform: none !important;
          }
        }
      `}</style>

      <div className="relative mx-auto max-w-3xl">
        <div className="hls-reveal">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
            Hover interaction
          </p>
          <h2 className="mt-4 text-3xl font-semibold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
            Links that answer back
          </h2>
          <p className="mt-4 max-w-xl text-base leading-relaxed text-slate-600 dark:text-slate-400">
            Each label rides up on hover and swaps for an identical copy tinted
            in the accent color. Keyboard focus triggers the exact same slide, so
            it reads the same whether you point or tab.
          </p>
        </div>

        <nav
          aria-label="Primary"
          className="hls-reveal mt-12 flex flex-wrap items-center gap-x-10 gap-y-4"
        >
          {navLinks.map((item, i) => (
            <SlideLink
              key={item.href}
              href={item.href}
              label={item.label}
              accent={
                i % 2 === 0
                  ? "text-indigo-600 dark:text-indigo-400"
                  : "text-violet-600 dark:text-violet-400"
              }
            />
          ))}
        </nav>

        <div className="hls-reveal mt-16 border-b border-slate-200 dark:border-slate-800">
          <ul className="flex flex-col">
            {destinations.map((item) => (
              <li key={item.href}>
                <DestinationRow label={item.label} href={item.href} />
              </li>
            ))}
          </ul>
        </div>

        <p className="mt-8 text-sm text-slate-500 dark:text-slate-500">
          Tab through the links above to see the slide respond to focus, not just
          the pointer.
        </p>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →