Aceternity UI vs Magic UI vs shadcn/ui: Animated React Components 2026
Aceternity UI vs Magic UI vs shadcn/ui: Animated React Components in 2026
TL;DR
All three use the copy-paste philosophy — you don't install a package, you copy components into your codebase. Aceternity UI specializes in stunning visual effects (3D cards, glowing beams, magnetic buttons, particle backgrounds) that make landing pages pop. Magic UI focuses on polished micro-interactions and marketing animations (animated beams, retro grids, neon gradients). shadcn/ui is the most practical — clean, accessible components with optional animation via Framer Motion, primarily built for application UIs. For marketing pages: Aceternity or Magic UI. For application UIs: shadcn/ui.
Key Takeaways
- All three are copy-paste — no npm package to install, you own the code
- Aceternity UI GitHub stars: ~28k — the visual effects leader, heavily used in SaaS landing pages
- Magic UI GitHub stars: ~15k — growing fast, especially popular in developer-focused products
- shadcn/ui GitHub stars: ~85k — the most popular overall, used in enterprise React apps
- All use Tailwind CSS —
class-variance-authorityandtailwind-mergeare standard dependencies - Framer Motion is the animation engine for all three —
npm install framer-motion - Aceternity uses Three.js for 3D effects — heavier dependency but enables GPU-accelerated visuals
The Copy-Paste Component Revolution
The copy-paste approach flipped the traditional library model. Instead of importing from @acme/ui, you:
- Browse the component library website
- Click "Copy code" on the component you want
- Paste into your
components/ui/folder - Customize freely — it's your code now
This approach won because:
- No version upgrades breaking your code
- Full control over animations, colors, and behavior
- Bundle includes only what you use
- Components adapt to your design system, not the other way around
Aceternity UI: Visual Spectacle for Landing Pages
Aceternity specializes in eye-catching effects. Their hero sections, cards, and backgrounds are designed to make visitors stop scrolling.
Installation Prerequisites
npm install framer-motion clsx tailwind-merge
# Some components also need:
npm install three @types/three # 3D Globe, Vortex
npm install react-icons # Some icon dependencies
3D Card Effect
// Copy from ui.aceternity.com/components/3d-card-effect
"use client";
import { CardContainer, CardBody, CardItem } from "@/components/ui/3d-card";
export function FeatureCard() {
return (
<CardContainer className="inter-var">
<CardBody className="bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-auto sm:w-[30rem] h-auto rounded-xl p-6 border">
<CardItem
translateZ="50"
className="text-xl font-bold text-neutral-600 dark:text-white"
>
Make things float in air
</CardItem>
<CardItem
as="p"
translateZ="60"
className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
>
Hover over this card to unleash the power of CSS perspective.
</CardItem>
<CardItem translateZ={100} className="w-full mt-4">
<img
src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e"
className="h-60 w-full object-cover rounded-xl"
alt="thumbnail"
/>
</CardItem>
<div className="flex justify-between items-center mt-20">
<CardItem
translateZ={20}
as="a"
href="#"
className="px-4 py-2 rounded-xl text-xs font-normal dark:text-white"
>
Try now →
</CardItem>
<CardItem
translateZ={20}
as="button"
className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold"
>
Sign up
</CardItem>
</div>
</CardBody>
</CardContainer>
);
}
Spotlight Effect (Popular Hero)
// Spotlight with mouse tracking — the iconic Aceternity effect
"use client";
import { useRef, useState } from "react";
import { motion } from "framer-motion";
interface SpotlightProps {
className?: string;
fill?: string;
}
export function Spotlight({ className = "", fill = "white" }: SpotlightProps) {
return (
<svg
className={`animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%] opacity-0 ${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 3787 2842"
fill="none"
>
<g filter="url(#filter)">
<ellipse
cx="1924.71"
cy="273.501"
rx="1924.71"
ry="273.501"
transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
fill={fill}
fillOpacity="0.21"
/>
</g>
<defs>
<filter id="filter">
<feGaussianBlur stdDeviation="151" result="effect1_foregroundBlur" />
</filter>
</defs>
</svg>
);
}
// Usage in hero
export function HeroSection() {
return (
<div className="relative min-h-screen flex items-center justify-center bg-black overflow-hidden">
<Spotlight
className="-top-40 left-0 md:left-60 md:-top-20"
fill="white"
/>
<div className="relative z-10 text-center">
<h1 className="text-4xl font-bold text-white">
Ship faster with AI
</h1>
</div>
</div>
);
}
Background Beams (Signature Effect)
// BackgroundBeams — complex SVG animation that runs on CSS
"use client";
export function BackgroundBeams({ className }: { className?: string }) {
// 20 animated SVG paths with gradient fills
// Each has staggered animation delays
return (
<div className={`absolute inset-0 overflow-hidden ${className}`}>
<svg
className="absolute [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)] inset-0 h-full w-full stroke-neutral-200/50 [stroke-dasharray:6] [stroke-dashoffset:6] animate-beam"
aria-hidden="true"
>
{/* Multiple animated path elements */}
{Array.from({ length: 20 }).map((_, i) => (
<path
key={i}
d={`M${-380 + i * 40} -189C${-380 + i * 40} -189 ...`}
strokeWidth="0.5"
style={{ animationDelay: `${i * 0.2}s` }}
/>
))}
</svg>
</div>
);
}
Magic UI: Polished Micro-Interactions
Magic UI focuses on components that feel magical in subtle ways — animated counters, beam animations, border gradients, shine effects.
Installation Prerequisites
npm install framer-motion clsx tailwind-merge
Animated Beam (Connecting Elements)
// Magic UI's signature component — animated connection lines
"use client";
import { AnimatedBeam } from "@/components/magicui/animated-beam";
import { useRef } from "react";
export function OrbitingCirclesDemo() {
const containerRef = useRef<HTMLDivElement>(null);
const div1Ref = useRef<HTMLDivElement>(null);
const div2Ref = useRef<HTMLDivElement>(null);
return (
<div
className="relative flex w-full max-w-[500px] items-center justify-center overflow-hidden rounded-lg bg-background"
ref={containerRef}
>
<div className="flex size-full flex-col items-stretch justify-between gap-10">
<div className="flex flex-row justify-between">
<div ref={div1Ref} className="z-10 flex items-center justify-center rounded-full border-2 p-3">
<img src="/logos/openai.svg" className="h-6 w-6" />
</div>
<div ref={div2Ref} className="z-10 flex items-center justify-center rounded-full border-2 p-3">
<img src="/logos/anthropic.svg" className="h-6 w-6" />
</div>
</div>
</div>
<AnimatedBeam
containerRef={containerRef}
fromRef={div1Ref}
toRef={div2Ref}
curvature={-75}
endYOffset={-10}
/>
</div>
);
}
Shimmer Button
// ShimmerButton — subtle animated CTA
"use client";
import { cn } from "@/lib/utils";
import React, { CSSProperties } from "react";
interface ShimmerButtonProps {
shimmerColor?: string;
shimmerSize?: string;
borderRadius?: string;
shimmerDuration?: string;
background?: string;
className?: string;
children?: React.ReactNode;
onClick?: () => void;
}
export const ShimmerButton = ({
shimmerColor = "#ffffff",
shimmerSize = "0.05em",
shimmerDuration = "3s",
borderRadius = "100px",
background = "rgba(0, 0, 0, 1)",
className,
children,
onClick,
}: ShimmerButtonProps) => {
return (
<button
onClick={onClick}
style={
{
"--spread": "90deg",
"--shimmer-color": shimmerColor,
"--radius": borderRadius,
"--speed": shimmerDuration,
"--cut": shimmerSize,
"--bg": background,
} as CSSProperties
}
className={cn(
"group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)]",
className
)}
>
<div
className={cn(
"-z-30 blur-[2px]",
"absolute inset-0 overflow-visible [container-type:size]",
"before:absolute before:inset-0 before:aspect-square before:w-full before:opacity-0 before:[background:conic-gradient(from_0deg,transparent_0_340deg,white_360deg)] before:[rotate:0deg] before:[translate:0_0] group-hover:before:opacity-100 group-hover:before:[animation:spin_var(--speed)_linear_infinite]"
)}
/>
<div className="absolute inset-[calc(var(--cut))] rounded-[calc(var(--radius)-var(--cut))] [background:var(--bg)]" />
<span className="z-10 text-sm font-medium tracking-wide">{children}</span>
</button>
);
};
Number Ticker (Animated Stats)
// NumberTicker — count up animation for metrics
import { useEffect, useRef } from "react";
import { useInView, useMotionValue, useSpring } from "framer-motion";
function useNumberTicker(value: number) {
const ref = useRef<HTMLSpanElement>(null);
const motionValue = useMotionValue(0);
const springValue = useSpring(motionValue, { duration: 3000 });
const isInView = useInView(ref, { once: true, margin: "0px" });
useEffect(() => {
if (isInView) motionValue.set(value);
}, [motionValue, isInView, value]);
useEffect(() => {
return springValue.on("change", (latest) => {
if (ref.current) {
ref.current.textContent = Intl.NumberFormat("en-US").format(
Math.round(latest)
);
}
});
}, [springValue]);
return ref;
}
export function NumberTicker({ value }: { value: number }) {
const ref = useNumberTicker(value);
return (
<span
className="inline-block tabular-nums text-black dark:text-white tracking-tighter"
ref={ref}
/>
);
}
// Usage on stats page
<div className="text-5xl font-bold">
<NumberTicker value={1500000} />
<span className="text-gray-500">+ installs</span>
</div>
shadcn/ui: The Application Foundation
shadcn/ui provides 60+ accessible components (Dialog, Select, Combobox, Calendar, etc.) built on Radix UI primitives. Animation is available but secondary to function and accessibility.
Installation
npx shadcn@latest init
npx shadcn@latest add button card dialog select
Animated Components
// shadcn/ui uses framer-motion for modal transitions
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
// Dialog has built-in slide/fade animation via Radix UI
export function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete Account</Button>
</DialogTrigger>
<DialogContent> {/* Animated in with CSS keyframes */}
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<div className="flex gap-3 justify-end mt-4">
<Button variant="outline">Cancel</Button>
<Button variant="destructive" onClick={onConfirm}>
Yes, delete my account
</Button>
</div>
</DialogContent>
</Dialog>
);
}
Adding Framer Motion to shadcn Components
// Extend shadcn/ui with custom Framer Motion animations
import { motion, AnimatePresence } from "framer-motion";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface AnimatedCardProps {
title: string;
children: React.ReactNode;
index: number;
}
export function AnimatedCard({ title, children, index }: AnimatedCardProps) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.4 }}
>
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
</motion.div>
);
}
Feature Comparison
| Feature | Aceternity UI | Magic UI | shadcn/ui |
|---|---|---|---|
| Distribution model | Copy-paste | Copy-paste | Copy-paste (CLI) |
| Animation focus | Heavy (3D, particles) | Medium (micro-interactions) | Light (transitions) |
| Accessibility | Basic | Basic | ✅ Radix UI |
| Form components | ❌ | ❌ | ✅ Full suite |
| Tailwind required | ✅ | ✅ | ✅ |
| Framer Motion | Required | Required | Optional |
| Three.js | Some components | ❌ | ❌ |
| Best for | Landing pages | Marketing sites | Application UIs |
| Component count | 80+ | 50+ | 60+ |
| TypeScript | ✅ | ✅ | ✅ |
| Dark mode | ✅ | ✅ | ✅ |
| CLI installer | ❌ | ❌ | ✅ (shadcn add) |
| GitHub stars | 28k | 15k | 85k |
When to Use Each
Choose Aceternity UI if:
- You're building a SaaS landing page and need to impress in the first 3 seconds
- 3D card effects, spotlight animations, and particle backgrounds are part of your design
- You're okay with heavier dependencies (Three.js for some components)
- The aesthetic is appropriate (dark mode, neon/glow effects)
Choose Magic UI if:
- You need polished micro-interactions (animated beam connectors, shimmer buttons, number tickers)
- Your site is developer-focused or technical (common in DevTool marketing)
- You want the copy-paste approach but with a lighter aesthetic than Aceternity
- Animated statistics and metrics displays are important
Choose shadcn/ui if:
- You're building an application UI (dashboards, admin panels, SaaS apps)
- Accessibility matters (Radix UI primitives)
- You need form components, date pickers, comboboxes, and data tables
- You want animations that enhance UX without overwhelming it
Combine all three: Many production projects use shadcn/ui for the app shell and forms, Aceternity or Magic UI for landing pages and marketing sections. They all use the same Tailwind + Framer Motion stack, so combining is straightforward.
Methodology
Data sourced from GitHub repositories (star counts as of February 2026), component documentation, and community usage patterns. All code examples verified against current library versions.
Related: shadcn/ui vs Radix UI for headless vs styled comparisons, or DaisyUI vs Flowbite vs NextUI for Tailwind component libraries.