Icon Slide Button

A button with a sliding arrow icon that appears on hover with a smooth fill background transition

buttoniconslidehoveranimatedarrow
Preview

Source Code

"use client";

import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface IconSlideButtonProps {
  children?: React.ReactNode;
  href?: string;
  newTab?: boolean;
  fontSize?: number;
  borderRadius?: string;
  bgColor?: string;
  bgHoverColor?: string;
  textColor?: string;
  textHoverColor?: string;
  fillColor?: string;
  iconColor?: string;
  iconHoverColor?: string;
  className?: string;
  onClick?: () => void;
}

const transition = {
  duration: 0.4,
  ease: [0.44, 0, 0.56, 1] as const,
};

export default function IconSlideButton({
  children = "Get Started",
  href,
  newTab = false,
  fontSize = 16,
  borderRadius = "999px",
  bgColor = "rgb(250, 250, 250)",
  bgHoverColor = "rgb(8, 8, 8)",
  textColor = "rgb(0, 0, 0)",
  textHoverColor = "rgb(255, 255, 255)",
  fillColor = "rgb(0, 0, 0)",
  iconColor = "rgb(0, 0, 0)",
  iconHoverColor = "rgb(255, 255, 255)",
  className = "",
  onClick,
}: IconSlideButtonProps) {
  const Component = href ? motion.a : motion.button;
  const linkProps = href
    ? { href, ...(newTab ? { target: "_blank", rel: "noopener noreferrer" } : {}) }
    : {};

  return (
    <Component
      {...linkProps}
      onClick={onClick}
      initial="idle"
      whileHover="hover"
      whileTap={{ scale: 0.97 }}
      className={`icon-slide-btn ${className}`}
      style={{
        position: "relative",
        display: "inline-flex",
        alignItems: "center",
        justifyContent: "center",
        overflow: "hidden",
        border: "none",
        cursor: "pointer",
        textDecoration: "none",
        borderRadius,
        backgroundColor: bgColor,
      }}
    >
      {/* Text */}
      <motion.span
        variants={{
          idle: { color: textColor, paddingLeft: 24, paddingRight: 24 },
          hover: { color: textHoverColor, paddingLeft: 20, paddingRight: 0 },
        }}
        transition={transition}
        style={{
          position: "relative",
          zIndex: 2,
          fontSize,
          fontWeight: 500,
          whiteSpace: "nowrap",
          lineHeight: 1,
          paddingTop: 15,
          paddingBottom: 15,
        }}
      >
        {children}
      </motion.span>

      {/* Arrow icon - slides in from right */}
      <motion.span
        variants={{
          idle: { opacity: 0, width: 0, marginRight: 0 },
          hover: { opacity: 1, width: fontSize + 4, marginRight: 20 },
        }}
        transition={transition}
        style={{
          position: "relative",
          zIndex: 2,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          flexShrink: 0,
          overflow: "hidden",
        }}
      >
        <motion.span
          variants={{
            idle: { x: -12, color: iconColor },
            hover: { x: 0, color: iconHoverColor },
          }}
          transition={transition}
          style={{ display: "flex", alignItems: "center", flexShrink: 0 }}
        >
          <ArrowRight size={fontSize} strokeWidth={2} />
        </motion.span>
      </motion.span>

      {/* Fill overlay - expands on hover */}
      <motion.span
        variants={{
          idle: {
            scale: 0,
            opacity: 0,
          },
          hover: {
            scale: 1,
            opacity: 1,
          },
        }}
        transition={transition}
        style={{
          position: "absolute",
          inset: 0,
          zIndex: 1,
          borderRadius,
          backgroundColor: fillColor,
          transformOrigin: "center center",
        }}
      />
    </Component>
  );
}