ButtonsSimpleApril 8, 2026

Button Bubble Arrow

On hover, a leading arrow circle scales in from the left while the duplicate arrow on the right scales out. The label slides from behind the right arrow using a translateX transition. Pure CSS.

View Full Demo →
demo.jsx
import BtnBubbleArrow from "./index.jsx";
import { btnBubbleArrow } from "@/content/buttons/demo-data.js";
import styles from "./demo.module.css";

export default function BtnBubbleArrowDemo() {
  return (
    <div className={styles.demo}>
      <div className={styles.hero}>
        <p className={styles.label}>No. 5 Studio</p>
        <h1 className={styles.heading}>Ready to start<br />a project?</h1>
        <BtnBubbleArrow {...btnBubbleArrow} />
      </div>
    </div>
  );
}
index.jsx
"use client";
import styles from "./styles.module.css";

const Arrow = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    width="100%"
    className={styles.svg}
  >
    <polyline
      points="18 8 18 18 8 18"
      fill="none"
      stroke="currentColor"
      strokeMiterlimit="10"
      strokeWidth="1.5"
    />
    <line
      x1="18"
      y1="18"
      x2="5"
      y2="5"
      fill="none"
      stroke="currentColor"
      strokeMiterlimit="10"
      strokeWidth="1.5"
    />
  </svg>
);

export default function BtnBubbleArrow({ buttons = [] }) {
  return (
    <div className={styles.group}>
      {buttons.map((btn, i) => (
        <a key={i} href={btn.href} className={styles.btn}>
          {/* Leading arrow — scales in from left on hover */}
          <div className={styles.arrow}>
            <Arrow />
          </div>

          {/* Label — slides from behind the duplicate arrow */}
          <div className={styles.content}>
            <span className={styles.text}>{btn.label}</span>
          </div>

          {/* Duplicate arrow — sits on top, scales out on hover */}
          <div className={`${styles.arrow} ${styles.arrowDuplicate}`}>
            <Arrow />
          </div>
        </a>
      ))}
    </div>
  );
}
demo.module.css
.demo {
  min-height: 100vh;
  background: #f5f5f0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 3rem;
  padding: 4rem 2rem;
}

.label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9b9b9b;
}

.heading {
  font-size: clamp(3rem, 8vw, 7rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: #1a1a1a;
}
styles.module.css
.group {
  display: flex;
  flex-wrap: wrap;
  gap: 3em;
  justify-content: center;
  font-size: 1.25rem;
  padding: 4rem 2rem;
  background: #f5f5f0;
  min-height: 100%;
  align-items: center;
}

.btn {
  border-radius: 10em;
  justify-content: center;
  align-items: center;
  font-size: 1em;
  text-decoration: none;
  display: flex;
  position: relative;
}

.arrow {
  color: #131313;
  background-color: #ff4c2f;
  border-radius: 10em;
  display: flex;
  flex-flow: row;
  justify-content: center;
  align-items: center;
  width: 3.75em;
  height: 3.75em;
  position: relative;
  transition: transform 0.735s cubic-bezier(0.625, 0.05, 0, 1);
  transform: scale(0) rotate(0.001deg);
  transform-origin: left;
}

/* Duplicate arrow — positioned absolute to right, starts visible, hides on hover */
.arrowDuplicate {
  background-color: #efeeec;
  position: absolute;
  right: 0;
  z-index: 2;
  transform: scale(1) rotate(0.001deg);
  transform-origin: right;
}

.svg {
  width: 40%;
  transition: transform 0.735s cubic-bezier(0.625, 0.05, 0, 1);
  transform: rotate(0.001deg);
}

.content {
  color: #efeeec;
  background-color: rgba(0, 0, 0, 0.4);
  border-radius: 10em;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 3.75em;
  padding-left: 2em;
  padding-right: 2em;
  position: relative;
  transition: transform 0.735s cubic-bezier(0.625, 0.05, 0, 1);
  transform: translateX(-3.75em) rotate(0.001deg);
}

.text {
  line-height: 1;
  white-space: nowrap;
}

/* ── Hover states ── */

.btn:hover .content {
  transform: translateX(0em) rotate(0.001deg);
}

.btn:hover .svg {
  transform: rotate(-45deg);
}

.btn:hover .arrow:not(.arrowDuplicate) {
  transform: scale(1) rotate(0.001deg);
}

.btn:hover .arrowDuplicate {
  transform: scale(0) rotate(0.001deg);
}