An animated tabs component built with Framer Motion's layoutId prop.
Uses mix-blend-mode: exclusion to invert the text as the pill passes over each label. Also includes Framer Motion's automatic scale correction for the border radius via the style prop.
Libraries used
- Framer Motion (10.9.1)
- Tailwind CSS (3.2.7)
Code
import { motion } from "framer-motion";
import { useState } from "react";
let tabs = [
  { id: "world", label: "World" },
  { id: "ny", label: "N.Y." },
  { id: "business", label: "Business" },
  { id: "arts", label: "Arts" },
  { id: "science", label: "Science" },
];
function AnimatedTabs() {
  let [activeTab, setActiveTab] = useState(tabs[0].id);
  return (
    <div className="flex space-x-1">
      {tabs.map((tab) => (
        <button
          key={tab.id}
          onClick={() => setActiveTab(tab.id)}
          className={`${
            activeTab === tab.id ? "" : "hover:text-white/60"
          } relative rounded-full px-3 py-1.5 text-sm font-medium text-white outline-sky-400 transition focus-visible:outline-2`}
          style={{
            WebkitTapHighlightColor: "transparent",
          }}
        >
          {activeTab === tab.id && (
            <motion.span
              layoutId="bubble"
              className="absolute inset-0 z-10 bg-white mix-blend-difference"
              style={{ borderRadius: 9999 }}
              transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
            />
          )}
          {tab.label}
        </button>
      ))}
    </div>
  );
}