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: