
Staggered Grid Entrance Reveal
Published
24 hand-coded CSS animated cards grouped by what triggers the motion — scroll entrances, mount-in reveals, click state changes, data arrival, and ambient idle loops. Covers staggered reveals, @starting-style mounts, View Transitions detail morphs, FLIP filter re-layout, add-to-cart morphs, count-up KPIs, and conic gradient borders. For pointer-driven card motion see card hover effects, 3D tilt and stacked cards. Scoped under .ac-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related33 CSS Card Hover Effects15 CSS 3D Tilt Hover Cards36 CSS Stacked Cards20 CSS Cards

Published

Published

Published

Published

Published

Published

Published

Published

height: auto — the oldest impossible thing in CSS — solved twice over. The modern route is one line, interpolate-size: allow-keywords, which makes intrinsic keywords animatable. The universal route is the grid-template-rows: 0fr → 1fr trick. Both are wired to real <details> elements, so keyboard, screen reader and find-in-page all work with zero JavaScript.Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

:has() and animatable grid-template-columns, so the whole interaction is declarative CSS — and it is fully keyboard-operable, which almost no version of this effect on the web is.Published
.ac-NN prefix, you can paste one of each onto the same page with no collisions. Four hover patterns are included here anyway (cursor spotlight, holographic foil, corner ribbon, horizontal accordion) because in those four the effect is the topic people search for, not an incidental hover state.animation-timeline: view(), which binds a keyframe animation to an element's position within the viewport rather than to wall-clock time: .card { animation: rise linear both; animation-timeline: view(); animation-range: entry 0% cover 30%; }. The animation runs off the main thread, so a long grid stays smooth even while JavaScript is busy. animation-range is what makes it feel right — entry 0% cover 30% means the animation starts as the card's top edge enters the viewport and finishes when it has covered 30 percent of it. The portable path is IntersectionObserver: observe each card, add a class on isIntersecting, and unobserve immediately so it fires once rather than every time the user scrolls back. Add rootMargin: '0px 0px -10% 0px' so the animation triggers slightly before the card is fully visible. For staggering, do not schedule timers — set animation-delay: calc(var(--i) * 60ms) from an index custom property, so the browser handles the sequencing. Keep total stagger under roughly 400ms or the last card feels broken rather than choreographed. Demos 01 and 03 implement both paths.height: auto could not be transitioned, so the workaround was animating max-height to a guessed value — which either clips tall content or eases at visibly the wrong speed because the transition is running across a range the content never uses. Two modern primitives fix it properly. interpolate-size: allow-keywords, set once on :root, makes intrinsic size keywords animatable everywhere, so a plain transition: height .3s to height: auto simply works. Or use calc-size(auto) directly on the target when you want it opt-in per component rather than global. Where neither is supported, the robust fallback is a CSS Grid trick rather than max-height: wrap the content in a container that transitions grid-template-rows from 0fr to 1fr, with the inner element set to overflow: hidden. That animates to true content height with no magic numbers and no measurement JavaScript. Whichever you use, the toggle should be a real <button> carrying aria-expanded, or a native <details> element which brings its own disclosure semantics. Demo 09 ships the pure-CSS version with the grid fallback.view-transition-name, and the browser captures before and after snapshots and interpolates between them — position, size, and border radius all morph automatically. For a same-page change, wrap the DOM update: document.startViewTransition(() => { updateTheDOM() }). For cross-document navigation, opt in with @view-transition { navigation: auto; } in CSS on both pages. Two rules people trip on. First, view-transition-name must be unique per transition — if a grid of twelve cards all share one name the browser bails out entirely, so assign the name only to the card being activated and remove it afterward. Second, the API degrades to an instant swap where unsupported, so gate any transition-specific styling behind @supports (view-transition-name: none) and never make the navigation itself depend on the animation completing. Astro's built-in view transitions and Next.js App Router both build on this same primitive. Demo 04 is the reference implementation.getBoundingClientRect(). Apply the DOM change. Measure again. Then apply a transform that moves each card back to where it started, and immediately transition that transform to zero, so the browser animates from old position to new using only compositor-friendly transforms rather than animating layout properties. The whole implementation is roughly 30 lines and it is what Isotope, Muuri and Framer Motion's layout animations all do internally. Two production details: read all the positions before writing any styles, so you do not trigger repeated layout recalculation inside the loop; and cap the animation around 300ms, because layout animations feel sluggish much faster than opacity animations do. For cards entering or leaving rather than moving, pair FLIP with @starting-style for the entry and a discrete-transition exit. Demo 06 implements this, and Demo 02 covers the entry case on its own.transform, opacity, filter, and registered custom properties — never width, height, top or left on the hot path, so animation work stays off the main thread and out of layout. Cumulative Layout Shift stays at zero because entrance animations move elements with transforms while their layout box is already reserved; nothing reflows. Interaction to Next Paint stays safe because every JavaScript handler is rAF-coalesced and scroll and pointer listeners are registered { passive: true }. On accessibility, all 24 demos honour prefers-reduced-motion: reduce — and importantly they degrade to the finished state rather than to nothing, so a user who has requested less motion still sees the content, just without the movement. Interactive controls are real <button> elements with visible :focus-visible rings at 2px minimum and 3:1 contrast, and hit targets of at least 44 by 44 CSS pixels. Auto-cycling content pauses on hover and on :focus-within, which WCAG 2.2.2 requires for anything that moves for more than five seconds.startViewTransition for the navigation morph, position measurement for FLIP. Those range from about 12 to 40 lines each, dependency-free, written as self-contained init functions. For comparison, the libraries usually reached for here are considerably heavier: Framer Motion is roughly 40KB gzipped and requires React, GSAP with ScrollTrigger around 70KB, AOS about 14KB, and Isotope roughly 34KB plus jQuery in older setups. If you are pulling in a full animation library purely for scroll-triggered card entrances, animation-timeline: view() or a 15-line IntersectionObserver replaces it outright. Where libraries still earn their weight is orchestrated timelines, physics-based springs, and complex interruptible sequences — none of which these patterns need..ac-NN selector, @property registration and keyframe is framework-agnostic, and the 13 pure-CSS demos need no porting work at all. For the 11 with JavaScript, the work is lifecycle placement. React: useEffect(() => { const cleanup = init(); return cleanup }, []) — register once on mount, tear down on unmount, and never re-register per render, which is the most common bug when porting scroll observers. Vue 3: onMounted plus onBeforeUnmount. Svelte: onMount(() => { init(); return cleanup }). Next.js App Router: the interactive demos need 'use client' since they touch browser APIs; the 13 pure-CSS ones render fine as server components. Astro: client:visible suits card grids well, since they are usually below the fold — and Astro's own view transitions build on the same API as Demo 04. For Tailwind v4, layout and spacing translate to utilities directly, while the parts with no utility equivalent — @property registrations, keyframes, animation-timeline, animation-range — belong in a @theme block. Each demo is 2 to 6KB of CSS cherry-picked.15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.
22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.
25 hand-coded CSS button groups: segmented controls, split buttons, pill radios, toggle groups, filter chips, floating action buttons, pricing toggles, Web3 wallet-connect grids, AI model pickers, icon toolbars — with Popover API, offset-path, View Transitions, @property, :has(), and every modern CSS primitive. Zero dependencies vs Radix/shadcn/Aceternity, WCAG 2.2 AA accessible, MIT licensed.