Back to Learn SVG Animation

CTA Button with Arrow Nudge and Press Compression

A compact, repeatable CTA pattern that feels crisp on desktop and touch: the arrow nudges 6px to the right on hover, the whole button compresses to 0.98 scale while pressed, and everything restores smoothly in 120ms using transform-only CSS for no layout shift.

Why transform-only CTA animations?

Animating layout properties like margin or left causes reflow and can produce janky results when the effect is repeated across many CTAs. Using transform for both the arrow nudge and press compression keeps the GPU in the driver’s seat, prevents layout shift, and is safe to reuse across an entire site.

Behavior summary

  • Hover (desktop): arrow translates 6px to the right.
  • Press (mouse/touch/keyboard active): button scales to 0.98 (2% compression).
  • Restore duration: 120ms for smooth recovery.
  • Accessible: keyboard focus-visible, respects prefers-reduced-motion, no layout shift.

HTML (semantic and reusable)

Use a semantic button with an inline SVG arrow so the pattern can be repeated without IDs and works with assistive tech.

<button class="cta" type="button">
  <span class="cta__label">Get started</span>
  <svg class="cta__icon" width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <path d="M5 12h14M13 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
  </svg>
</button>

CSS (transform-based, CSS-only)

The CSS below is intentionally small, self-contained, and friendly to repeat across a site. Key points:

  • Use CSS variables to make the scale, nudge distance and timing easy to reuse.
  • Scope hover nudge to devices that actually support hover via @media (hover: hover) to avoid accidental behavior on touch.
  • Use transform for both the parent (scale) and child (translate) to prevent layout changes.
  • Respect prefers-reduced-motion to disable transitions for users who requested reduced motion.
:root{
  --cta-scale-press: 0.98;
  --cta-arrow-nudge: 6px; /* 6px to the right */
  --cta-transition: 120ms; /* restore duration */
  --cta-ease: cubic-bezier(.2,.8,.2,1);
}

/* Basic visual layout */
.cta{
  display: inline-flex;
  align-items: center;
  gap: 0.5rem; /* keeps label and icon spaced without layout changes */
  padding: 0.6rem 1rem;
  font-size: 1rem;
  line-height: 1;
  border: none;
  border-radius: 8px;
  background: #0b76ff;
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent; /* cleaner touch feel */
  touch-action: manipulation; /* faster tapping on some mobile browsers */

  /* performance hint and transform origin */
  will-change: transform;
  transform-origin: center center;

  /* transition controls restore timing */
  transition: transform var(--cta-transition) var(--cta-ease);
}

/* The SVG arrow is moved using transform only */
.cta__icon{
  display: block;
  width: 1em;
  height: 1em;
  color: currentColor;
  transform: translateX(0);
  transition: transform var(--cta-transition) var(--cta-ease);
  will-change: transform;
}

/* Press / active compression */
.cta:active,
.cta[data-pressed="true"]{ /* optional hook if you need programmatic press state */
  transform: scale(var(--cta-scale-press));
}

/* Hover nudge only on devices that support hover */
@media (hover: hover) and (pointer: fine) {
  .cta:hover .cta__icon,
  .cta:focus-visible .cta__icon{
    transform: translateX(var(--cta-arrow-nudge));
  }
}

/* Keep clear focus styles for keyboard users */
.cta:focus-visible{
  outline: 3px solid rgba(255,255,255,0.18);
  outline-offset: 2px;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce){
  .cta,
  .cta__icon{
    transition: none !important;
    will-change: auto;
  }
}

Notes and progressive enhancement

  • Immediate press feel: modern browsers will start animating the transform on :active. The 120ms transition produces a quick compression and smooth restore. If you want the compression to feel instantaneous you can lower the transition on :active specifically (e.g., add a shorter transition for entering the pressed state), but the simple approach above is robust and consistent.
  • Keyboard support: :focus-visible triggers the nudge for keyboard users and the :active press will work if the user activates via space/enter.
  • Touch behavior: hover nudge is disabled for touch-only devices by the media query; tap produces the press compression and then returns to normal in 120ms. The CSS is safe to repeat across many buttons because it only uses transforms.
  • Repeating across a site: keep these classes small and reusable (e.g., .cta, .cta__icon, .cta__label) and they can be applied to any primary button surface without layout shifts or reflow cost.

Testing checklist

Before shipping, quickly confirm:

  1. Desktop mouse: hover nudges arrow 6px, press compresses to 0.98 and restores smoothly.
  2. Keyboard: focus-visible shows nudge, enter/space triggers press compression.
  3. Mobile touch: no accidental hover; tap compresses and restores in ~120ms.
  4. Performance: no layout shift, smooth 60fps animation on supported hardware.

Wrap-up

This transform-only approach gives you a small, accessible CTA pattern that feels responsive and repeatable site-wide. The arrow nudge of 6px and 0.98 press compression are subtle but provide a strong affordance that the button is interactive — all without causing layout shifts or heavy paint cost.

Rendered example: