Back to Learn SVG Animation

Feature Card Icon Loop with Staggered Parts

A lightweight, CSS-only pattern for feature grid cards: this example shows an SVG icon split into three parts that lift and rotate 4° in a staggered sequence (420ms total) on hover or keyboard focus, keeping CPU cost low and accessibility intact.

Feature Card Icon Loop with Staggered Parts (CSS-only)

This pattern animates a compact SVG feature icon made of three independent parts. When a user hovers or focuses the card, each SVG part lifts and rotates 4° in a gentle staggered sequence that finishes in a 420ms window. The technique uses only transforms and transitions for low CPU cost and supports keyboard focus via a focusable card element.

Why this approach?

  • Transforms only: translate + rotate avoid layout and paint work so the animation stays GPU-friendly.
  • CSS sequencing: simple transition delays create a stagger without JS.
  • Keyboard friendly: focus produces the same motion as hover.
  • Low overhead for feature grids: use contain/will-change and short transitions to reduce per-card cost.

Markup (HTML)

Use a semantic interactive element (button or anchor) for keyboard accessibility. The example below uses a button with an inline SVG broken into three groups (.part--1, .part--2, .part--3).

<button class="feature-card" type="button" aria-label="Open feature">
  <svg class="feature-icon" width="64" height="64" viewBox="0 0 64 64" aria-hidden="true">
    <g class="part part--1">
      <rect x="10" y="8" width="44" height="12" rx="3" />
    </g>
    <g class="part part--2">
      <rect x="10" y="26" width="44" height="12" rx="3" />
    </g>
    <g class="part part--3">
      <rect x="10" y="44" width="44" height="12" rx="3" />
    </g>
  </svg>
  <span class="feature-label">Feature name</span>
</button>

CSS (core animation + accessibility)

Key points: all motion uses transform, each .part has the same 420ms duration and is offset with a staggered transition-delay so the whole sequence occupies ~420ms (0ms, 140ms, 280ms). The card responds to both :hover and :focus-visible so keyboard users get the same effect. We also respect prefers-reduced-motion.

:root{
  --lift-y: -6px;              /* vertical lift */
  --rotate-deg: 4deg;         /* rotation amount */
  --dur: 420ms;               /* transition duration for each part */
  --stagger-step: 140ms;      /* delay between parts -> total span 280 + duration => visual 420ms sequence */
}

.feature-card{
  color: black;
  width: 200px;
  height: 120px;
  display:inline-flex;
  flex-direction:column;
  align-items:center;
  gap:.5rem;
  border:0;
  background:transparent;
  padding:.6rem;
  cursor:pointer;
  /* containment helps isolate paint costs in dense grids */
  contain: layout paint size;
}

.feature-icon{ width:64px; height:64px; display:block; }

.part{
  transition: transform var(--dur) cubic-bezier(.22,.9,.3,1);
  transform-origin: 50% 60%; /* pivot value tuned to look natural */
  will-change: transform;
}

/* stagger delays for the three parts */
.part--1{ transition-delay: 0ms; }
.part--2{ transition-delay: var(--stagger-step); }
.part--3{ transition-delay: calc(var(--stagger-step) * 2); }

/* Hover and keyboard focus state: lift + rotate. Both selectors ensure parity for mouse & keyboard */
.feature-card:hover .part,
.feature-card:focus-visible .part{
  transform: translateY(var(--lift-y)) rotate(var(--rotate-deg));
}

/* Optional subtle pop: slightly change delays when leaving so pieces settle gracefully */
.feature-card:not(:hover):not(:focus-visible) .part{
  /* Make sure the return respects the same durations/delays in reverse order if needed */
}

/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce){
  .part{ transition: none !important; transform:none !important; }
}

Notes and tuning

  • Stagger timing: the example uses 140ms steps so parts start at 0 / 140 / 280 ms — each part's transition lasts 420ms, producing a continuous-looking 420ms spread. If you prefer a tighter or longer cascade, tweak --stagger-step and --dur.
  • Transform origin: change transform-origin to adjust the pivot of the rotation. For icons with bottoms anchored, a bottom-centered pivot (50% 100%) can feel different than the center pivot used above.
  • Containment: using contain: paint (or layout paint size) reduces repainting outside the element, which helps in dense grids. Browser support varies—test in your target browsers.
  • will-change: the property hints to the browser to create a composite layer for transforms. Keep it scoped to the animated parts and avoid applying it globally.

Accessibility & keyboard behavior

Make the card an interactive element (button or anchor) so keyboard users can tab to it. Use :focus-visible to avoid focus styling on mouse clicks yet ensure keyboard visibility. Include aria-labels as needed. Also respect prefers-reduced-motion so users who requested reduced motion receive a non-animated experience.

Performance checklist for feature grids

  • Animate only transform properties (translate/rotate/scale) to avoid layout and paint.
  • Keep durations short (200–500ms) and stagger steps small to prevent long animation overlap across many cards.
  • Use contain + will-change narrowly to limit compositing work per card.
  • Test on real low-powered devices; if many cards animate simultaneously consider limiting animations to only the hovered/focused card (this pattern already does that).

With this CSS-only approach you get a pleasant, accessible micro-interaction for feature cards that scales safely in dense grids while remaining easy to customize.

 Rendered example: