Text Swipe Button
A minimalist text-only button that smoothly swipes its label upwards when hovered, replacing it with an identical sliding label.
React Code
"use client";
import React, { useState } from "react";
import { motion, useReducedMotion } from "framer-motion";
export function TextSwipeButton() {
const shouldReduceMotion = useReducedMotion();
const [isHovered, setIsHovered] = useState(false);
return (
<motion.button
type="button"
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
onFocus={() => setIsHovered(true)}
onBlur={() => setIsHovered(false)}
whileTap={shouldReduceMotion ? {} : { scale: 0.95 }}
className="relative w-[89px] h-[19px] overflow-hidden outline-none cursor-pointer select-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[#1F2123] bg-transparent border-none p-0 block"
>
{/* Default Text (Swipes up and out) */}
<motion.span
initial={false}
animate={{ top: isHovered ? "-21px" : "0px" }}
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
className="absolute left-[10px] font-work font-medium text-[16px] text-[#1F2123] leading-[1.2] whitespace-nowrap"
>
continue
</motion.span>
{/* Hidden Text (Swipes up into view) */}
<motion.span
initial={false}
animate={{ top: isHovered ? "0px" : "25.5px" }}
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
className="absolute left-[10px] font-work font-medium text-[16px] text-[#1F2123] leading-[1.2] whitespace-nowrap"
>
continue
</motion.span>
</motion.button>
);
}