Back to library
cards
Feature Block Animated Card
Feature card with pulsing icon row, a sweeping cyan scan line, and drifting sparkles. Great for bento/feature grids.
Inspired by Manu Arora · View source
Preview
Ship faster with edge infra
Deploy globally distributed services in seconds — no config, no cold starts.
Copy for AI
Copy for Lovable is the fastest way to move this component into another Lovable project — it copies the full prompt with dependencies, files, and a usage example. Copy all files gives you the raw source if you want to paste it manually.
Installation
npx shadcn@latest add https://21st.dev/r/manuarora700/feature-block-animated-cardPaste into a terminal — or paste into a Lovable chat and I'll run it for you.
Source files
components/ui/feature-block-animated-card.tsx
"use client"
import { animate, motion } from "framer-motion"
import React, { useEffect } from "react"
import { cn } from "@/lib/utils"
import "./feature-block-animated-card.css"
export interface AnimatedCardProps {
className?: string
title?: React.ReactNode
description?: React.ReactNode
icons?: Array<{
icon: React.ReactNode
size?: "sm" | "md" | "lg"
className?: string
}>
}
const sizeMap = {
sm: "h-8 w-8",
md: "h-12 w-12",
lg: "h-16 w-16",
}
export function AnimatedCard({ className, title, description, icons = [] }: AnimatedCardProps) {
return (
<div
className={cn(
"max-w-sm w-full mx-auto p-8 rounded-xl border border-[rgba(255,255,255,0.10)] dark:bg-[rgba(40,40,40,0.70)] bg-gray-100 shadow-[2px_4px_16px_0px_rgba(248,248,248,0.06)_inset] group",
className
)}
>
<div
className={cn(
"h-[15rem] md:h-[20rem] rounded-xl z-40",
"bg-neutral-300 dark:bg-[rgba(40,40,40,0.70)] [mask-image:radial-gradient(50%_50%_at_50%_50%,white_0%,transparent_100%)]"
)}
>
<AnimatedIcons icons={icons} />
</div>
{title && (
<h3 className="text-lg font-semibold text-gray-800 dark:text-white py-2">
{title}
</h3>
)}
{description && (
<p className="text-sm font-normal text-neutral-600 dark:text-neutral-400 max-w-sm">
{description}
</p>
)}
</div>
)
}
function AnimatedIcons({ icons = [] }: { icons: AnimatedCardProps["icons"] }) {
const scale = [1, 1.1, 1]
const transform = ["translateY(0px)", "translateY(-4px)", "translateY(0px)"]
const sequence = icons.map((_, index) => [
`.circle-${index + 1}`,
{ scale, transform },
{ duration: 0.8 },
])
useEffect(() => {
animate(sequence as never, {
repeat: Infinity,
repeatDelay: 1,
})
}, [])
return (
<div className="p-8 overflow-hidden h-full relative flex items-center justify-center">
<div className="flex flex-row flex-shrink-0 justify-center items-center gap-2">
{icons.map((icon, index) => (
<Container
key={index}
className={cn(
sizeMap[icon.size || "lg"],
`circle-${index + 1}`,
icon.className
)}
>
{icon.icon}
</Container>
))}
</div>
<AnimatedSparkles />
</div>
)
}
const Container = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
`rounded-full flex items-center justify-center bg-[rgba(248,248,248,0.01)]
shadow-[0px_0px_8px_0px_rgba(248,248,248,0.25)_inset,0px_32px_24px_-16px_rgba(0,0,0,0.40)]`,
className
)}
{...props}
/>
))
Container.displayName = "Container"
const AnimatedSparkles = () => (
<div className="h-40 w-px absolute top-20 m-auto z-40 bg-gradient-to-b from-transparent via-cyan-500 to-transparent animate-move">
<div className="w-10 h-32 top-1/2 -translate-y-1/2 absolute -left-10">
<Sparkles />
</div>
</div>
)
// Deterministic pseudo-random values so SSR and client markup match.
const seeded = (n: number) => {
const x = Math.sin(n * 12.9898) * 43758.5453
return x - Math.floor(x)
}
const stars = [...Array(12)].map((_, i) => ({
top: seeded(i + 1) * 100,
left: seeded(i + 21) * 100,
toTop: seeded(i + 41) * 100,
toLeft: seeded(i + 61) * 100,
move: seeded(i + 81) * 2 - 1,
opacity: seeded(i + 101),
duration: seeded(i + 121) * 2 + 4,
}))
const Sparkles = () => {
return (
<div className="absolute inset-0">
{stars.map((star, i) => (
<motion.span
key={`star-${i}`}
animate={{
top: `calc(${star.toTop}% + ${star.move}px)`,
left: `calc(${star.toLeft}% + ${star.move}px)`,
opacity: star.opacity,
scale: [1, 1.2, 0],
}}
transition={{
duration: star.duration,
repeat: Infinity,
ease: "linear",
}}
style={{
position: "absolute",
top: `${star.top}%`,
left: `${star.left}%`,
width: `2px`,
height: `2px`,
borderRadius: "50%",
zIndex: 1,
}}
className="inline-block bg-black dark:bg-white"
/>
))}
</div>
)
}
Dependencies
framer-motionlucide-react