diff --git a/animata/text/roll-text.css b/animata/text/roll-text.css index c7dae398..c4134d0b 100644 --- a/animata/text/roll-text.css +++ b/animata/text/roll-text.css @@ -19,12 +19,16 @@ white-space: pre; } - /* inline-grid + overflow:hidden keeps the text baseline (from the sizer in - row 1) while clipping the translated stack. */ + /* inline-grid + clip keeps the text baseline (from the sizer in row 1) + while clipping the translated stack. Prefer clip-path/contain over plain + overflow:hidden — sticky headers with backdrop-filter promote compositing + layers that can let transformed children paint outside overflow:hidden. */ .roll-unit { position: relative; display: inline-grid; - overflow: hidden; + overflow: clip; + clip-path: inset(0); + contain: paint; vertical-align: baseline; font-kerning: none; line-height: inherit; diff --git a/animata/text/ticker.tsx b/animata/text/ticker.tsx index a00226a5..403d05fd 100644 --- a/animata/text/ticker.tsx +++ b/animata/text/ticker.tsx @@ -1,8 +1,7 @@ "use client"; import { motion, useInView, useMotionValue, useSpring } from "motion/react"; - -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useLayoutEffect, useRef } from "react"; import { cn } from "@/lib/utils"; @@ -32,31 +31,40 @@ function Number({ const isRaw = String(+value) !== value; - useEffect(() => { - if (!isInView || isRaw || !numberRef.current) { - return; - } + useLayoutEffect(() => { + if (!isInView || isRaw) return; - const update = () => { - const height = getHeight(); - springValue.set(-height * +value); - // Add a delay to prevent the spring from firing too early. - }; + const height = getHeight(); + if (!height) return; + const target = -height * +value; + + // Correct digit on first paint — avoid the "0,000+" trust flash. if (!delay) { - update(); + motionValue.jump(target); return; } - const timer = setTimeout(update, (total - index) * Math.floor(Math.random() * delay)); + motionValue.jump(0); + const timer = setTimeout( + () => { + springValue.set(target); + }, + (total - index) * Math.floor(Math.random() * delay), + ); return () => clearTimeout(timer); - }, [value, isRaw, isInView, springValue, getHeight, index, total, delay]); + }, [value, isRaw, isInView, springValue, motionValue, getHeight, index, total, delay]); if (isRaw) { return {value}; } + // Static digit until in view so overflow strip never shows a wall of 0–9. + if (!isInView) { + return {value}; + } + return ( >, +) { + useEffect(() => { + if (paused) return; + + const interval = setInterval(() => { + setIndex((current) => { + const next = current + direction; + if (direction === TypingDirection.Forward) return Math.min(next, total); + return Math.max(next, 0); + }); + }, stepMs); + + return () => clearInterval(interval); + }, [direction, stepMs, total, paused, setIndex]); +} + +function useTypingEndpoint( + atEnd: boolean, + atStart: boolean, + direction: TypingDirection, + repeat: boolean | undefined, + waitTime: number, + setDirection: Dispatch>, + onCompleteRef: MutableRefObject<(() => void) | undefined>, + completedRef: MutableRefObject, +) { + useEffect(() => { + if (!atEnd && !atStart) return; + + if (atEnd && direction === TypingDirection.Forward) { + if (!repeat) { + if (!completedRef.current) { + completedRef.current = true; + onCompleteRef.current?.(); + } + return; + } + + const timeout = setTimeout(() => { + setDirection(TypingDirection.Backward); + }, waitTime); + return () => clearTimeout(timeout); + } + + if (atStart && direction === TypingDirection.Backward && repeat) { + const timeout = setTimeout(() => { + setDirection(TypingDirection.Forward); + }, waitTime); + return () => clearTimeout(timeout); + } + }, [atEnd, atStart, direction, repeat, waitTime, setDirection, onCompleteRef, completedRef]); +} + function CursorWrapper({ visible, children, @@ -162,65 +230,38 @@ function Type({ hideCursorOnComplete, }: TypingTextProps) { const [index, setIndex] = useState(0); - const directionRef = useRef(TypingDirection.Forward); + const [direction, setDirection] = useState(TypingDirection.Forward); const onCompleteRef = useRef(onComplete); const completedRef = useRef(false); onCompleteRef.current = onComplete; const words = useMemo(() => text.split(/\s+/), [text]); const total = smooth ? words.length : text.length; - const isComplete = index === total && !repeat; + const stepMs = Math.max(1, delay ?? 32); + const caret = Math.min(Math.max(index, 0), total); + if (index !== caret) { + setIndex(caret); + } + const isComplete = caret === total && !repeat; + const atEnd = caret >= total; + const atStart = caret <= 0; + const paused = + (atEnd && direction === TypingDirection.Forward) || + (atStart && direction === TypingDirection.Backward); - useEffect(() => { - let interval: ReturnType | undefined; - let timeout: ReturnType | undefined; - - const startInterval = () => { - interval = setInterval(() => { - setIndex((current) => { - const direction = directionRef.current; - const next = current + direction; - - if (direction === TypingDirection.Forward && next >= total) { - if (!repeat) { - if (!completedRef.current) { - completedRef.current = true; - onCompleteRef.current?.(); - } - if (interval) clearInterval(interval); - return total; - } - if (interval) clearInterval(interval); - timeout = setTimeout(() => { - directionRef.current = TypingDirection.Backward; - startInterval(); - }, waitTime); - return total; - } - - if (direction === TypingDirection.Backward && next <= 0) { - if (interval) clearInterval(interval); - timeout = setTimeout(() => { - directionRef.current = TypingDirection.Forward; - startInterval(); - }, waitTime); - return 0; - } - - return next; - }); - }, delay); - }; - - startInterval(); - - return () => { - if (interval) clearInterval(interval); - if (timeout) clearTimeout(timeout); - }; - }, [total, delay, repeat, waitTime]); - - const waitingNextCycle = index === total || index === 0; + useTypingInterval(paused, direction, total, stepMs, setIndex); + useTypingEndpoint( + atEnd, + atStart, + direction, + repeat, + waitTime, + setDirection, + onCompleteRef, + completedRef, + ); + + const waitingNextCycle = caret === total || caret === 0; return (
@@ -231,9 +272,9 @@ function Type({ })} > {smooth ? ( - + ) : ( - + )} (null); @@ -40,22 +39,17 @@ export default function CallToActionSection() { weeks of work.

-
+
Get started now - - - View on GitHub - +
diff --git a/app/(main)/_landing/exit-intent-modal.tsx b/app/(main)/_landing/exit-intent-modal.tsx index 8bff4602..4f15b5b4 100644 --- a/app/(main)/_landing/exit-intent-modal.tsx +++ b/app/(main)/_landing/exit-intent-modal.tsx @@ -2,17 +2,17 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"; import { Cross2Icon } from "@radix-ui/react-icons"; -import { ArrowUpRight, Loader2, Mail } from "lucide-react"; +import { Loader2 } from "lucide-react"; import type React from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { siteConfig } from "@/config/site"; import useExitIntent from "@/hooks/use-exit-intent"; import useNewsletterSubscription from "@/hooks/use-newsletter-subscription"; function NewsletterInline() { - const { isLoading, error, success, addSubscriber, setEmail, email } = useNewsletterSubscription(); + const { isLoading, error, success, addSubscriber, setEmail, email } = + useNewsletterSubscription("exit_intent"); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -22,30 +22,37 @@ function NewsletterInline() { if (success) { return (

- Subscribed. We'll keep it short. + Subscribed. New components when they ship.

); } return ( -
- setEmail(e.target.value)} - className="flex-1 border-border bg-background text-sm" - /> - - {error &&

{error}

} -
+
+
+ setEmail(e.target.value)} + className="flex-1 border-border bg-background text-sm" + /> + +
+ {error ? ( +

{error}

+ ) : ( +

No spam. Unsubscribe anytime.

+ )} +
); } @@ -56,49 +63,20 @@ export default function ExitIntentModal() { - - {/* Header */} +
- - Didn't find the right component? + + Get new components by email - Tell us what to build next. + One short note when something new ships.
-
- {/* Request a component */} -
-

Missing something?

-

- Open an issue and we'll build it. -

- - Request on GitHub - - -
- - {/* Newsletter signup */} -
-

- - Or just follow along -

-

- We send new components when they're ready. That's it. -

- -
+
+
- {/* Close button */} Close diff --git a/app/(main)/_landing/home-page.tsx b/app/(main)/_landing/home-page.tsx index 71a0c752..829f3d86 100644 --- a/app/(main)/_landing/home-page.tsx +++ b/app/(main)/_landing/home-page.tsx @@ -4,13 +4,13 @@ import Link from "next/link"; import React, { Suspense } from "react"; import CarbonAds from "@/components/ads"; -import { Icons } from "@/components/icons"; +import { GitHubStarLink } from "@/components/github-star-link"; import { docsConfig } from "@/config/docs"; -import { siteConfig } from "@/config/site"; import { siteStats } from "@/config/site-stats"; import { cn } from "@/lib/utils"; import ExitIntentModal from "./exit-intent-modal"; +import NewsletterSection from "./newsletter"; import OpenSourceSection from "./open-source-section"; import StatsBento from "./stats-bento"; @@ -55,29 +55,20 @@ function Hero() { Free, open source, and ready to use.

-
+
Explore components - - - Star on GitHub - +
-

- - {siteStats.githubStarsFormatted} stars · Trusted by shipping teams -

- + {/* Carbon cover unit must stay above the fold per publisher docs */}
@@ -156,9 +147,11 @@ export default function HomePage() { + + -
+

Ready to make your interfaces stand out?

diff --git a/app/(main)/_landing/newsletter.tsx b/app/(main)/_landing/newsletter.tsx index 92f90c70..97a94a23 100644 --- a/app/(main)/_landing/newsletter.tsx +++ b/app/(main)/_landing/newsletter.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Loader2, Mail } from "lucide-react"; import type React from "react"; @@ -11,11 +13,14 @@ import { cn } from "@/lib/utils"; function NewsletterInput({ compact = false, brand = false, + source = "newsletter", }: { compact?: boolean; brand?: boolean; + source?: string; }) { - const { isLoading, error, success, addSubscriber, setEmail, email } = useNewsletterSubscription(); + const { isLoading, error, success, addSubscriber, setEmail, email } = + useNewsletterSubscription(source); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -46,11 +51,9 @@ function NewsletterInput({ > setEmail(e.target.value)} /> @@ -59,20 +62,20 @@ function NewsletterInput({ disabled={isLoading} className={cn( "shrink-0 bg-[hsl(var(--accent))] text-white shadow-none hover:bg-[hsl(var(--accent))]/90! hover:text-white!", - compact ? "h-9 rounded-none px-4" : "w-full sm:w-auto", + compact ? "h-10 rounded-md px-5" : "w-full sm:w-auto", )} > {isLoading && } - {isLoading ? "Please wait" : "Join now"} + {isLoading ? "Please wait" : "Subscribe"}

{success ? ( - Thank you for subscribing! + Subscribed. New components when they ship. ) : error ? ( {error} ) : ( - "100% free. No spam. No noise. Unsubscribe at any time." + "No spam. Unsubscribe anytime." )}

@@ -82,25 +85,51 @@ function NewsletterInput({ type NewsletterSectionProps = { compact?: boolean; brand?: boolean; + /** Full-width homepage block (not the card or footer variant). */ + featured?: boolean; + source?: string; }; export default function NewsletterSection({ compact = false, brand = false, + featured = false, + source, }: NewsletterSectionProps) { if (brand) { return ( -
-

- Sign up for updates on new components and releases. +

+

+ New components when they ship.

- +
); } + if (featured) { + return ( +
+
+

+ New components when they ship. +

+

+ Starred the repo? Get one short email per release. No spam. +

+
+ +
+
+
+ ); + } + return ( - Stay in the loop + New components when they ship - New components, tips, and updates. No spam. + One short email per release. No spam. - + ); diff --git a/app/(main)/_landing/open-source-section.tsx b/app/(main)/_landing/open-source-section.tsx index f5d308d4..32af3b97 100644 --- a/app/(main)/_landing/open-source-section.tsx +++ b/app/(main)/_landing/open-source-section.tsx @@ -2,6 +2,7 @@ import Marquee from "@/animata/container/marquee"; import Counter, { Formatter } from "@/animata/text/counter"; +import { GitHubStarLink } from "@/components/github-star-link"; import { siteStats } from "@/config/site-stats"; // All 44 contributors @@ -125,11 +126,24 @@ export default function OpenSourceSection() { {/* Stats */}
- +
+ + {/* Marquee row 2 — all contributors, reversed order + direction */}
diff --git a/app/(main)/_landing/stats-bento.tsx b/app/(main)/_landing/stats-bento.tsx index dcdfea9a..473ca164 100644 --- a/app/(main)/_landing/stats-bento.tsx +++ b/app/(main)/_landing/stats-bento.tsx @@ -159,12 +159,10 @@ export default function StatsBento() {
- - - +

GitHub stars diff --git a/components/footer-subscribe.tsx b/components/footer-subscribe.tsx index 07744dba..79f5c811 100644 --- a/components/footer-subscribe.tsx +++ b/components/footer-subscribe.tsx @@ -1,19 +1,18 @@ "use client"; -import { ArrowUpRight } from "@phosphor-icons/react"; import { Loader2 } from "lucide-react"; import { type FormEvent, useId } from "react"; import { cn } from "@/lib/utils"; const subscribeFieldClassName = - "flex h-8 max-h-8 min-h-8 min-w-0 flex-1 items-center rounded-sm border border-border bg-white px-2.5 shadow-none transition-colors focus-within:border-[hsl(var(--accent))]/35 dark:bg-background"; + "flex h-10 min-w-0 flex-1 items-center rounded-md border border-border bg-white px-3 shadow-none transition-colors focus-within:border-[hsl(var(--accent))]/35 dark:bg-background"; const subscribeInputClassName = - "w-full border-0 bg-transparent text-xs leading-none text-foreground outline-none placeholder:font-normal placeholder:text-muted-foreground focus-visible:outline-none"; + "w-full border-0 bg-transparent text-sm leading-none text-foreground outline-none placeholder:font-normal placeholder:text-muted-foreground focus-visible:outline-none"; const subscribeButtonClassName = - "inline-flex size-8 shrink-0 touch-manipulation items-center justify-center rounded-full border border-border bg-white text-foreground shadow-none transition-colors hover:text-[hsl(var(--accent))] disabled:pointer-events-none disabled:opacity-40 dark:bg-background"; + "inline-flex h-10 shrink-0 touch-manipulation items-center justify-center rounded-md border border-border bg-[hsl(var(--accent))] px-3.5 text-xs font-semibold text-white shadow-none transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40"; type FooterSubscribeProps = { email: string; @@ -35,17 +34,21 @@ export function FooterSubscribe({ className, }: FooterSubscribeProps) { const inputId = useId().replace(/:/g, ""); - const statusText = success ? "Thank you for subscribing!" : error; + const statusText = success + ? "Subscribed. New components when they ship." + : error + ? error + : "No spam. Unsubscribe anytime."; return (

-
+ -

- {statusText ?? "\u00a0"} + {statusText}

); diff --git a/components/github-star-link.tsx b/components/github-star-link.tsx new file mode 100644 index 00000000..0de6f5f2 --- /dev/null +++ b/components/github-star-link.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { ComponentPropsWithoutRef, ReactNode } from "react"; + +import { Icons } from "@/components/icons"; +import { siteConfig } from "@/config/site"; +import { trackEvent } from "@/lib/events"; +import { withOutboundRef } from "@/lib/outbound-ref"; +import { cn } from "@/lib/utils"; + +type GitHubStarLinkProps = Omit, "href" | "onClick"> & { + source: string; + children?: ReactNode; + showIcon?: boolean; +}; + +/** Shared star CTA — same label/destination, tracked for conversion. */ +export function GitHubStarLink({ + source, + className, + children = "Star to follow releases", + showIcon = true, + ...props +}: GitHubStarLinkProps) { + return ( + trackEvent({ name: "github_star_click", properties: { source } })} + {...props} + > + {showIcon ? : null} + {children} + + ); +} diff --git a/components/mobile-nav.tsx b/components/mobile-nav.tsx index 9ec83307..111810d7 100644 --- a/components/mobile-nav.tsx +++ b/components/mobile-nav.tsx @@ -11,7 +11,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { docsConfig } from "@/config/docs"; import { siteConfig } from "@/config/site"; -import { brandLabelClassName } from "@/lib/brand-font"; +import { brandLabelClassName } from "@/lib/brand-label"; import { getFooterCategories } from "@/lib/docs"; import { cn } from "@/lib/utils"; @@ -82,19 +82,18 @@ export function MobileNav() {
- {docsConfig.mainNav?.map( - (item) => - item.href && ( - - {item.title} - - ), - )} + {docsConfig.mainNav + ?.filter((item) => item.href && item.href !== "/") + .map((item) => ( + + {item.title} + + ))}
void) { window.addEventListener("scroll", callback, { passive: true }); return () => window.removeEventListener("scroll", callback); @@ -29,6 +28,9 @@ function getScrollServerSnapshot() { return false; } +/** Home is the logo — don't duplicate as an "Index" nav item. */ +const headerNavItems = docsConfig.mainNav.filter((item) => item.href !== "/"); + export function SiteHeader() { const pathname = usePathname(); const isIndexPage = pathname === "/"; @@ -49,29 +51,42 @@ export function SiteHeader() {
-
-
+
+
+ + + {/* Logo art is optically high in the viewBox — nudge down to match cap height */} + + {siteConfig.name} + +
-
+
- + + + Star + {siteStats.githubStarsCompact} + + Star on GitHub, {siteStats.githubStarsFormatted} stars + +
diff --git a/config/docs.ts b/config/docs.ts index 1197f74d..c5f7700b 100644 --- a/config/docs.ts +++ b/config/docs.ts @@ -59,12 +59,18 @@ const sidebarNav: SidebarNavItem[] = [ href: "/docs/guides/animated-react-buttons", items: [], }, + { + title: "Animated React Text Effects", + href: "/docs/guides/animated-react-text-effects", + items: [], + }, ], }, { title: "Changelog", href: "/docs/changelog", items: [ + { title: "September 2026", href: "/docs/changelog/2026-09", items: [] }, { title: "June 2026", href: "/docs/changelog/2026-06", items: [] }, { title: "May 2026", href: "/docs/changelog/2026-05", items: [] }, { title: "April 2026", href: "/docs/changelog/2026-04", items: [] }, diff --git a/config/site-stats.ts b/config/site-stats.ts index f9d15383..34ad398b 100644 --- a/config/site-stats.ts +++ b/config/site-stats.ts @@ -3,7 +3,7 @@ import type { docs as allDocs } from "#site/content"; import { getPublishedDocs } from "@/lib/published-docs"; /** Updated manually when marketing copy needs to reflect GitHub stars. */ -const GITHUB_STARS = 2697; +const GITHUB_STARS = 2802; const NON_COMPONENT_SECTIONS = new Set(["contributing", "changelog"]); @@ -24,9 +24,18 @@ function formatStatPlus(value: number) { return `${value.toLocaleString("en-US")}+`; } +function formatStarsCompact(value: number) { + if (value >= 1000) { + const compact = (value / 1000).toFixed(value >= 10_000 ? 0 : 1).replace(/\.0$/, ""); + return `${compact}k`; + } + return value.toLocaleString("en-US"); +} + export const siteStats = { githubStars: GITHUB_STARS, githubStarsFormatted: formatStatPlus(GITHUB_STARS), + githubStarsCompact: formatStarsCompact(GITHUB_STARS), componentCount: publishedComponentCount, componentsFormatted: formatStatPlus(publishedComponentCount), } as const; diff --git a/content/blog/aceternity-ui-vs-magic-ui-vs-animata.mdx b/content/blog/aceternity-ui-vs-magic-ui-vs-animata.mdx new file mode 100644 index 00000000..69c05d09 --- /dev/null +++ b/content/blog/aceternity-ui-vs-magic-ui-vs-animata.mdx @@ -0,0 +1,197 @@ +--- +title: "Aceternity UI vs Magic UI vs Animata: Which Animated React Kit for Shipping Teams?" +date: 2026-09-14 +dateModified: 2026-09-14 +description: "Compare Aceternity UI, Magic UI, and Animata for animated React: when to use each for landing pages vs product UI, ownership, Motion, and accessibility." +seoTitle: "Aceternity UI vs Magic UI vs Animata (2026)" +seoDescription: "Neutral comparison of Aceternity UI, Magic UI, and Animata for animated React — landing vs product UI, free tiers, Motion, accessibility, and when to mix kits with shadcn." +author: AnimataDesign +labels: + - Comparison + - React + - Shipping + - Aceternity UI + - Magic UI + - shadcn +published: true +featured: true +--- + +Choose by surface: marketing landing page versus in-app product UI. Demo reels alone are a weak pick criterion. + +**Aceternity UI** focuses on cinematic landing surfaces—heroes, shaders, logo clouds, bento layouts, and high-impact Motion for marketing pages ([ui.aceternity.com](https://ui.aceternity.com/)). **Magic UI** focuses on polished marketing micro-interactions: 150+ free open-source animated components, Design Engineer craft, and a companion layer for shadcn/ui ([magicui.design](https://magicui.design/)). **Animata** focuses on product UI you own: 155+ free MIT components for in-app chrome (buttons, cards, tabs, scroll, text), copy/registry ownership, with keyboard focus, screen-reader labels, and reduced-motion patterns documented as part of the paste surface ([animata.design/components](https://animata.design/components)). + +All three ship React + Tailwind animated components into a real codebase. They are not interchangeable. Many teams mix kits by surface—and that is often the right call. + +## Summary — which kit for which surface? + +| | **Aceternity UI** | **Magic UI** | **Animata** | +| --- | --- | --- | --- | +| **Best for** | Cinematic landing pages | Polished marketing micro-interactions | Product UI polish you own | +| **Free model** | Free components + paid All-Access for premium blocks/templates | 150+ free OSS components; Pro for 50+ blocks/templates | 155+ components, MIT, free forever | +| **Motion** | Built around Motion for high-impact effects | Motion for marketing-grade animation | Motion optional for some complex animations; not zero-Motion forever | +| **Accessibility** | Verify focus, labels, and reduced motion after paste (same as most marketing kits) | Verify focus, labels, and reduced motion in your product context | Focus, screen-reader labels, and reduced-motion fallbacks included before copy; still verify in your app | +| **Typical surface** | Heroes, shaders, logo clouds, bento, landing templates | Landing sections, marquees, effects next to shadcn | Buttons, cards, tabs, skeletons, lists, progress, scroll, text | + +Combine kits when it helps: Aceternity or Magic on marketing; Animata in-app; shadcn for form/data primitives. Shared Tailwind and `cn` make that viable—do not merge configs blindly. + +## What each library is + +### Aceternity UI — cinematic landing effects + +[Aceternity UI](https://ui.aceternity.com/) is a React + Tailwind + Motion library aimed at landing pages. The site positions **200+ production-ready components, blocks, and templates**—copy, paste, customize, and ship without rebuilding heavy animation and styling from scratch. + +Aceternity is strong on **cinematic marketing surface area**: hero sections, shaders, logo clouds, feature and bento layouts, backgrounds, and other high-impact Motion effects for first impressions. Free components are available to explore and use; **All-Access** unlocks premium blocks and templates (hero collections, shader blocks, logo clouds, feature sections, backgrounds, bento grids, and full templates—confirm current All-Access contents and pricing on the site at publish time). + +There is also an **MCP / AI agent workflow**: paste components yourself, or connect an agent to the Aceternity UI MCP server. For launch pages, that visual range plus copy/paste plus agent tooling is a practical advantage. + +### Magic UI — polished marketing motion + shadcn companion + +[Magic UI](https://magicui.design/) describes itself as a UI library for Design Engineers: **150+ free and open-source animated components and effects** built with React, TypeScript, Tailwind CSS, and Motion—and a **companion for shadcn/ui**. + +Magic's strength is **marketing micro-interaction quality**: polish that makes landing sections feel intentional without always going full cinematic shader/hero. If your stack already centers on shadcn primitives, Magic sits beside that stack as the animated marketing layer. **Magic UI Pro** adds **50+ blocks and templates** for building landing pages faster (verify current Pro scope and pricing on the site at publish time). + +For teams that want tight craft and motion as product language on marketing surfaces, Magic belongs on the shortlist. + +### Animata — product-first animated React you own + +[Animata](https://animata.design/components) ships **155+ animated React components**, free and open source under **MIT—free forever**. Components were used in real apps first, then documented. The install model matches the ownership model many teams already know from shadcn: **skip a heavy package install; copy the file and own it**. + +Animata targets **product UI polish**: buttons, cards, text, bento grids, scroll patterns, and other chrome people use repeatedly inside the app—not only on the marketing homepage. Accessibility is part of the paste surface: **keyboard focus, screen-reader labels, and reduced-motion fallbacks** are included before you copy. Tailwind is required; `cn` via `clsx` + `tailwind-merge`. Motion is **optionally required for some complex animations**—not a promise of zero Motion forever. Setup docs spell that out: [docs/setup](https://animata.design/docs/setup). + +Worth knowing before you quote a single component count: the homepage currently frames **155+** components ([animata.design](https://animata.design/)), while the agent catalog at [llms.txt](https://animata.design/llms.txt) summarizes the library as **~130**. Prefer the live page you care about at publish. Same for Motion—check the component page before assuming every paste is CSS-only. + +Framework fit includes Next.js, Remix, Vite, Astro, Gatsby, and TanStack. The homepage claims **2,802+ GitHub stars** (verified against GitHub 2026-09-14; prefer linking the [GitHub repo](https://github.com/codse/animata) and re-verify if citing metrics). + +## Side-by-side + +| Dimension | Aceternity UI | Magic UI | Animata | +| --- | --- | --- | --- | +| **Primary use case** | Cinematic landing pages and high-impact marketing blocks | Polished marketing micro-interactions; Design Engineer craft | In-app / product UI animated chrome with ownership | +| **License / paid tier** | Free components; All-Access for premium blocks/templates (verify pricing on site) | Free OSS core (150+); Pro for 50+ blocks/templates (verify pricing on site) | MIT; free forever | +| **Install model** | Copy/paste; MCP/AI agent workflow | Copy/paste style animated components; Pro for kits | Copy file and own it (shadcn-like); registry `shadcn add` URLs | +| **Tailwind** | Yes | Yes | Required; `cn` via clsx + tailwind-merge | +| **Motion** | Core to the high-impact aesthetic | Used for marketing-grade animation | Optional for some complex animations | +| **Bundle / effect weight** | Some effects are heavier (shaders, globe-style, canvas-style visuals)—scope per component | Generally marketing effects; check per component | Lean product chrome; Motion when a complex animation needs it | +| **Accessibility / reduced motion** | Verify after paste: focus, labels, contrast, reduced motion | Verify for product contexts; catalog is marketing-first | Focus, screen-reader labels, reduced-motion fallbacks before copy; still verify in your app | +| **Best paired with** | Marketing sites, launch pages, studio/SaaS templates | shadcn/ui + marketing sections | shadcn primitives for forms/data + Animata for animated product chrome | + +*Cells that depend on vendor pricing pages or exact dependency trees should be re-checked at publish.* + +## When to use each + +### Aceternity UI + +Reach for Aceternity when the job is **first-impression cinema**: heroes that need to hold attention, shader backgrounds, logo clouds with motion, bento feature layouts, and landing templates that already look like a finished marketing site. The free component catalog is generous for exploration; All-Access is the path when you want premium blocks and full templates without designing every section from scratch. + +The MCP and AI agent angle matters for teams that already compose UI with agents: Aceternity is a paste-and-assemble system with an official agent workflow, not only a gallery. + +Tradeoffs to plan for: cinematic kits carry **aesthetic and bundle weight**. A marketing hero can feel out of place inside dense product chrome. After paste, accessibility is **your responsibility**—keyboard path, focus rings, contrast, and `prefers-reduced-motion`. Some Aceternity effects are intentionally heavy (shaders, globe-style visuals, canvas-style cards). Use them where that cost pays off; avoid pasting every high-impact effect into authenticated app chrome by default. + +### Magic UI + +Reach for Magic UI when you want **marketing micro-interactions that feel engineered**, not only decorative. The Design Engineer framing matches the catalog. It is especially strong if you already use **shadcn/ui**—Magic positions itself as the companion layer for animated marketing components and effects on top of that foundation. + +Use the free open-source catalog for individual animated pieces. Use **Magic UI Pro** when you need broader landing blocks and templates to assemble pages faster (site currently highlights **50+** blocks and templates—confirm Pro contents and pricing at publish). + +Tradeoffs to plan for: Magic is **marketing-forward**. That is a strength on landing pages and product marketing sections; it is not automatically the right source of truth for every in-app control. Motion is part of the stack—budget for bundle and performance reviews the same way you would for any Motion-heavy marketing UI. For full landing kits, expect Pro rather than assuming every template lives in the free tier. + +### Animata + +Reach for Animata when the surface is **product chrome**: animated buttons, cards, tabs, skeletons, lists, progress, scroll sections, and text effects that need to feel alive without turning the app into a landing page. Docs hubs include [/docs/button](https://animata.design/docs/button), [/docs/card](https://animata.design/docs/card), [/docs/text](https://animata.design/docs/text), [/docs/bento-grid](https://animata.design/docs/bento-grid), and [/docs/scroll/stacked-sections](https://animata.design/docs/scroll/stacked-sections). There is also a practical buttons guide: [Animated React buttons](https://animata.design/docs/guides/animated-react-buttons). + +Animata fits teams that want ownership (copy the file, edit freely), an accessibility starting point before paste, framework flexibility (Next.js through Vite, Remix, Astro, Gatsby, TanStack), and a registry workflow via Animata registry URLs. + +If your pain is that app UI feels static but you still need accessible, maintainable React, Animata is built for that lane. + +### When not to use Animata + +Do not pick Animata for cinematic 3D / shader heroes—that is Aceternity's primary surface. Animata is not positioned as a full substitute for cinematic landing catalogs. Do not pick Animata as a drop-in for Magic's signature marketing catalog either; if you specifically want Magic's Design Engineer marketing effects next to shadcn, use Magic. And do not expect Animata to replace a full form/data primitive system—start with [shadcn/ui](https://ui.shadcn.com/), then layer Animata where motion polish helps. + +Using the wrong kit for the surface creates thrash. Match job to library. + +## Mixing kits with shadcn + +Yes—and many teams should. + +A practical stack looks like this: + +1. **shadcn/ui** for form and data primitives (dialogs, inputs, tables, menus). +2. **Aceternity and/or Magic** for marketing homepage and launch sections. +3. **Animata** for animated in-app chrome (buttons, cards, scroll, text, product micro-interactions). + +They share a cultural model: **Tailwind**, copy-owned source, and usually a `cn` helper. That makes mixing viable. + +Do not merge configs blindly. Keep Tailwind content paths honest, avoid duplicate utility pipelines, and align Motion versions. Treat each pasted component as owned code: trim unused effects, align tokens, and run the same accessibility checks as hand-written UI. Mixing is composition—not install-everything. + +## Accessibility and reduced motion as a decision axis + +Animated UI fails quietly when it ignores keyboard users, screen-reader labels, and `prefers-reduced-motion`. Treat that as a pick criterion, not a post-ship ticket. + +Aceternity and Magic deliver strong visual craft. After paste, **you** own the accessibility audit—focus order, visible focus, contrast, motion alternatives, and whether decorative animation is correctly ignored by assistive tech. That is normal for most marketing-oriented kits. Animata ships with **keyboard focus, screen-reader labels, and reduced-motion fallbacks included before copy**, so the starting point is closer to typical product requirements. You still verify in your app shell (theme tokens, layout wrappers, nested dialogs, and so on). + +Adjacent note: libraries like [Motiq](https://motiq.dev) sit in the accessibility / reduced-motion conversation for motion-minded teams. Whatever kit you choose, reduced motion is part of shipping. For Animata's own product direction and iteration notes, see [Improving Animata](https://animata.design/blog/improving-animata). + +## Getting started with Animata + +Read setup first: [https://animata.design/docs/setup](https://animata.design/docs/setup)—Tailwind, `cn` (clsx + tailwind-merge), framework notes. Browse components at [https://animata.design/components](https://animata.design/components). Then add via registry when you want shadcn-style install of a specific component: + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/button/ai-button.json +pnpm dlx shadcn@latest add https://animata.design/r/button/ripple-button.json +``` + +Or copy the file into your project and own it—edit motion, tokens, and copy to match your design system. Start with one high-traffic control (primary button or empty-state card), confirm reduced-motion, then expand category by category. + +"Copy-paste" here means you own the file (or the registry install). Same shadcn-style model: skip a heavy package for the component itself; copy or add via a registry URL and edit freely. + +## Adjacent kits (short) + +Same shortlist, not full substitutes: + +- **[React Bits](https://reactbits.dev)** — more animated React components. +- **[Cult UI](https://cult-ui.com)** — adjacent design-system craft. +- **[Motiq](https://motiq.dev)** — accessibility / reduced-motion adjacent. +- **[shadcn/ui](https://ui.shadcn.com/)** — primitives many teams pair with Magic (marketing) and Animata (product motion). + +## FAQ + +### What is the difference between Aceternity UI, Magic UI, and Animata? + +Aceternity UI is strongest for **cinematic landing pages**—heroes, shaders, logo clouds, bento, and high-impact Motion blocks ([ui.aceternity.com](https://ui.aceternity.com/)). Magic UI is strongest for **polished marketing micro-interactions**, with 150+ free open-source animated components and a strong shadcn/ui companion story ([magicui.design](https://magicui.design/)). Animata is strongest for **product UI polish you own**: 155+ MIT components with copy-and-own source and accessibility/reduced-motion patterns included before paste ([animata.design](https://animata.design/components)). Same general stack family (React, Tailwind, Motion where needed); different primary surfaces. + +### Is Animata a free open-source Aceternity alternative? + +For **product UI animated components that you own**, yes—Animata is a free, MIT, open-source option with 155+ components and no paid tier required for the core library. It is **not** a 1:1 substitute for Aceternity's cinematic landing catalog (shaders, globe-style effects, premium marketing templates). If you need Aceternity's landing-page range, use Aceternity (free components + All-Access for premium). If you need accessible in-app motion chrome, use Animata. Many teams use both. + +### When should I use each? + +**Aceternity** for marketing heroes and high-impact landing sections; All-Access when you want premium blocks/templates. **Magic** for marketing micro-interactions and shadcn-adjacent Design Engineer polish; Pro for fuller landing kits. **Animata** for buttons, cards, tabs, skeletons, lists, progress, scroll, and text inside the product; MIT and free forever. + +### Is Animata free forever? + +Yes. Animata's animated React components are **free, open source, MIT—free forever** for the core library described on the site. + +### Do I need Motion for Animata? + +**Not for everything.** Motion is **optionally required for some complex animations** ([docs/setup](https://animata.design/docs/setup)). Plan on Motion for those pieces; do not assume every Animata component forces Motion forever. + +### Which frameworks does Animata work with? + +Next.js, Remix, Vite, Astro, Gatsby, and TanStack—see [setup](https://animata.design/docs/setup). + +### Magic UI vs Animata for app UI? + +For **in-app product chrome** with ownership and accessibility/reduced-motion as a paste default, prefer **Animata**. For **marketing sections** and Design Engineer micro-interactions—especially beside shadcn—prefer **Magic**. They can coexist: Magic on the marketing site, Animata in the authenticated product. + +### How many components / stars should I trust? + +Prefer vendor pages at publish: Aceternity **200+** components/blocks/templates; Magic **150+** free; Animata **155+** on the homepage ([animata.design](https://animata.design/)). The agent catalog noted above may list a different approximate count (~130)—treat counts as verify-at-publish, not frozen marketing. Animata has **2,802+** GitHub stars ([github.com/codse/animata](https://github.com/codse/animata), checked 2026-09-14). Magic Pro is currently framed as **50+** blocks and templates—confirm on the vendor site. + +## Bottom line + +Choose **Aceternity** when you need cinematic landing surfaces, premium marketing blocks, and an MCP/AI-assisted paste workflow. Choose **Magic** when you want polished marketing micro-interactions and a Design Engineer / shadcn companion stack—Pro when you need fuller landing kits. Choose **Animata** when the job is product UI animated React you own, with accessibility and reduced-motion treated as shipping defaults rather than afterthoughts. + +Ship the right kit on the right surface. Then compose: shadcn for primitives, Aceternity or Magic for marketing impact, Animata for the product chrome people use every day. + +**Next step:** [Browse Animata components](https://animata.design/components) · [Setup](https://animata.design/docs/setup) · [Top animated React text effects](https://animata.design/docs/guides/animated-react-text-effects) · Compare on the vendors' sites: [Aceternity UI](https://ui.aceternity.com/) · [Magic UI](https://magicui.design/) diff --git a/content/docs/changelog/2026-09.mdx b/content/docs/changelog/2026-09.mdx new file mode 100644 index 00000000..b0e9f2bd --- /dev/null +++ b/content/docs/changelog/2026-09.mdx @@ -0,0 +1,23 @@ +--- +title: September 2026 +description: Homepage conversion fixes plus SEO guides comparing animated React kits and text effects. +date: 2026-09-01 +--- + +Tightened the homepage conversion path for GitHub stars and email subscribe: clearer asks, equal hero CTAs, an earlier newsletter block, a header star badge, and tracked subscribe/star events in PostHog. + +Published an [Aceternity UI vs Magic UI vs Animata](/blog/aceternity-ui-vs-magic-ui-vs-animata) comparison and a [Top Animated React Text Effects (2026)](/docs/guides/animated-react-text-effects) guide for search and answer-engine discovery. + +## Site + +Rewrote star CTAs to lead with why (“Star to follow releases”), made the hero star control a full-size button on mobile, and replaced the icon-only header GitHub link with a visible Star · count pill. + +Moved the featured newsletter up (after open source), added a “Just starred?” bridge to email, and made footer/exit-intent subscribe controls use a visible Subscribe button with risk-reversal copy. Made exit intent email-first and added mobile scroll-depth and time-on-page triggers. + +Hardened the stars ticker so it shows the real count on first paint instead of a `0,000+` flash. Added `newsletter_subscribe` and `github_star_click` PostHog events so conversion can be measured against home sessions. + +## Docs & blog + +Added a neutral surface-based comparison of Aceternity UI, Magic UI, and Animata (landing vs product UI, free tiers, Motion, accessibility, mixing with shadcn). + +Added a curated text-effects guide covering ten MIT presets with registry installs, Motion vs CSS guidance, accessibility notes, and ecosystem context. diff --git a/content/docs/changelog/index.mdx b/content/docs/changelog/index.mdx index 1ad81c11..dc47a5b4 100644 --- a/content/docs/changelog/index.mdx +++ b/content/docs/changelog/index.mdx @@ -10,6 +10,10 @@ New components, bug fixes, and infrastructure changes, going back to the beta la ## Recent releases +### [September 2026](/docs/changelog/2026-09) + +Homepage conversion fixes for GitHub stars and newsletter subscribe — equal hero CTAs, earlier email opt-in, header Star pill, ticker first-paint fix, and PostHog conversion events. Published [Aceternity UI vs Magic UI vs Animata](/blog/aceternity-ui-vs-magic-ui-vs-animata) and [Top Animated React Text Effects](/docs/guides/animated-react-text-effects). + ### [June 2026](/docs/changelog/2026-06) [Stacked Sections](/docs/scroll/stacked-sections), polished Wave 1 widget glance affordances, added throw motion to [Card Spread](/docs/card/card-spread), trimmed the [Card Stack](/docs/card/card-stack) API, cleaned up the catalog to published widgets only, published a [buttons guide](/docs/guides/animated-react-buttons), and finished a react-doctor pass to zero errors with pnpm hardening and CI. @@ -24,6 +28,7 @@ New components, bug fixes, and infrastructure changes, going back to the beta la | Month | Highlights | |---|---| +| [September 2026](/docs/changelog/2026-09) | Homepage star + newsletter conversion fixes, PostHog events | | [June 2026](/docs/changelog/2026-06) | Stacked Sections, polished Wave 1 widgets, Card Spread throw motion, trimmed Card Stack API, buttons guide, react-doctor remediation, unpublished four oversized widgets | | [May 2026](/docs/changelog/2026-05) | Tailwind 4.3, Boids Ecosystem, Sibling Focus Nav, live demos, docs typography | | [April 2026](/docs/changelog/2026-04) | 19 text animation presets, shadcn registry, announcement ribbon, site revamp | diff --git a/content/docs/guides/animated-react-text-effects.mdx b/content/docs/guides/animated-react-text-effects.mdx new file mode 100644 index 00000000..0f5923b3 --- /dev/null +++ b/content/docs/guides/animated-react-text-effects.mdx @@ -0,0 +1,252 @@ +--- +title: "Top Animated React Text Effects (2026)" +description: "Curated animated React text effects for Tailwind — typewriter, split, gradient, flip, and scroll reveals. Copy-paste or shadcn registry; MIT; a11y-minded." +seoTitle: "Top Animated React Text Effects (2026) — Copy-Paste Tailwind Components" +seoDescription: "Ten animated React text effects for Tailwind: typewriter, wave reveal, flip, gradient, split, scroll reveal, circular, glitch, gibberish, and shimmer. MIT copy-paste or shadcn registry." +date: 2026-09-14 +dateModified: 2026-09-14 +toc: true +published: true +labels: + - Guide + - Text + - React + - Tailwind +--- + +Start by picking one text preset, installing it via registry or paste, verifying reduced-motion behavior, then shipping. Rebuild SplitText in Motion or GSAP only when the brief needs a custom letter timeline. + +[Animata](https://animata.design/docs/text) is the copy-own lane for that: MIT text presets, registry installs, and accessibility patterns (sr-only full strings, aria-hidden decorative layers, reduced-motion hooks) documented before you copy. Many presets use the Web Animations API (WAAPI) via a shared `text-animator` runtime; others are plain React state, CSS keyframes, or Motion when scroll or continuous layout motion needs it. This guide is the text sibling of Animata's [animated React buttons](https://animata.design/docs/guides/animated-react-buttons) guide—practical, fair to the rest of the ecosystem, and clear about Motion versus CSS. + +Worth knowing up front: not every Animata text effect needs Motion, and Motion is not ruled out either. Typing Text, Wave Reveal, Text Flip, and several others are CSS/React/WAAPI; Scroll Reveal and Circular Text require Motion. Setup states Motion is optionally required for some complex animations ([docs/setup](https://animata.design/docs/setup); per-component docs under [docs/text](https://animata.design/docs/text)). Component counts can also drift—homepage **155+** ([animata.design](https://animata.design/)) versus agent catalog **~130** at [llms.txt](https://animata.design/llms.txt)—prefer the live page at publish. And "copy-paste" / registry means you own the source: edit timing, tokens, and copy freely. + +## How to choose + +Match the job to the stack before you paste. Effects fail when the interaction model does not match the surface—for example, a hover-only split on a mobile hero, or Motion for a gradient clip that CSS already handles. + +| Job | Pick | Avoid when | +| --- | --- | --- | +| Typewriter / terminal / AI "thinking" copy | [Typing Text](https://animata.design/docs/text/typing-text) | You need scroll-scrubbed typography—use Scroll Reveal or Motion/GSAP instead | +| Letter/word entrance with optional blur | [Wave Reveal](https://animata.design/docs/text/wave-reveal) | You refuse to add the docs' `@keyframes` / `@theme` snippet | +| Cycling headline words | [Text Flip](https://animata.design/docs/text/text-flip) | You need physics-grade letter trails—reach for Motion/GSAP custom SplitText | +| Brand gradient accent on a label | [Animated Gradient Text](https://animata.design/docs/text/animated-gradient-text) | The effect must track scroll progress (gradient ≠ scroll reveal) | +| Hover split / letter drama | [Split Text](https://animata.design/docs/text/split-text) | Touch-first surfaces with no hover—plan a static fallback | +| Scroll-linked opacity on copy | [Scroll Reveal](https://animata.design/docs/text/scroll-reveal) | You want zero Motion dependency | +| Rotating circular path lockups | [Circular Text](https://animata.design/docs/text/circular-text) | Continuous loops without a reduced-motion plan | +| RGB glitch / high-contrast display type | [Glitch Text](https://animata.design/docs/text/glitch-text) | Body-scale copy (the effect is for display type) | +| Decode / "AI reveal" from noise | [Gibberish Text](https://animata.design/docs/text/gibberish-text) | You need scroll-driven choreography | +| Continuous marketing shimmer / WAAPI preset | [Shimmer Sweep](https://animata.design/docs/text/shimmer-sweep) | You cannot accept the shared `text-animator` runtime (or looping motion without a fallback) | + +## Ten paste-ready picks + +Each one is MIT, documented, and installable with the registry URL under the pick. Browse the full category when you are done: [animata.design/docs/text](https://animata.design/docs/text). + +### 1. Typing Text + +Classic typewriter for heroes, empty states, and AI product chrome. It drives the caret with React state and effects—cursor blink, smooth word mode, repeat/wait—so you get a readable loop without a Motion dependency. Own the file and tune timing to your brand voice. Reach for it on hero typewriters, CLI-style marketing, onboarding prompts, and "generating…" affordances. + +Docs: [Typing Text](https://animata.design/docs/text/typing-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/typing-text.json +``` + +### 2. Wave Reveal + +Letter- or word-by-word entrance with an optional blur. Animation is CSS (`reveal-up` / `reveal-down` / `content-blur`); the component also ships an `sr-only` copy of the full string so assistive tech gets the message once while glyphs animate visually. Paste the `@keyframes` / `@theme` block from the docs—skip that and the entrance never runs. + +Strong on section titles, feature intros, and headline entrances. [Wave Reveal docs](https://animata.design/docs/text/wave-reveal). + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/wave-reveal.json +``` + +### 3. Text Flip + +Cycling headline words via CSS `flip-words` keyframes—swap "Ship / Scale / Iterate" without orchestrating Motion timelines. React owns the word list; CSS owns the flip. No Motion package required. + +Use it for rotating value props, short marketing phrases, and hero subheads. [Text Flip docs](https://animata.design/docs/text/text-flip). + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/text-flip.json +``` + +### 4. Animated Gradient Text + +Tailwind gradient clip plus CSS keyframes for a brand accent that moves. Lean stack: no animation engine, just utilities and a small keyframe loop you can theme. Product names, badge labels, pricing highlights, light marketing accents—this is the quiet polish pick. + +[Animated Gradient Text docs](https://animata.design/docs/text/animated-gradient-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/animated-gradient-text.json +``` + +### 5. Split Text + +Hover split drama: letters open vertically around the pointer with neighborhood falloff. Decorative letter layers are `aria-hidden`; an invisible sizing layer keeps layout stable. This is pointer-dependent—pair with a static (non-hover) presentation on touch-first layouts. Desktop marketing lockups and playful brand moments only; do not make critical copy hover-gated. + +[Split Text docs](https://animata.design/docs/text/split-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/split-text.json +``` + +### 6. Scroll Reveal + +Scroll-linked opacity on flattened children—sticky copy that brightens as the user scrolls the container. This one **requires Motion** (`motion/react`) and is honest about it: scroll progress values are Motion's job. Long-form storytelling, manifesto blocks, product narratives. + +[Scroll Reveal docs](https://animata.design/docs/text/scroll-reveal) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/scroll-reveal.json +``` + +### 7. Circular Text + +Rotating circular-path lockups for badges, seals, and "scroll for more" chrome. Continuous loops look great in demos—respect `prefers-reduced-motion` so the ring does not spin forever for every user. **Requires Motion**. + +[Circular Text docs](https://animata.design/docs/text/circular-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/circular-text.json +``` + +### 8. Glitch Text + +RGB-split glitch: magenta/cyan ghosts jitter while the base label stays sharp and readable. Ghosts are `aria-hidden`; co-located CSS includes `@media (prefers-reduced-motion: reduce)` so ghosts stop and fade. The CLI installs the TSX component and co-located CSS together. Keep it on display type—404s, high-contrast marketing, error/status labels—not body copy. + +[Glitch Text docs](https://animata.design/docs/text/glitch-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/glitch-text.json +``` + +### 9. Gibberish Text + +Decode from gibberish into real copy—useful for demos, generative UIs, and launch teasers. Implemented with React intervals, not a Motion timeline. Fast to own; easy to overuse. One moment per screen is usually enough. + +[Gibberish Text docs](https://animata.design/docs/text/gibberish-text) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/gibberish-text.json +``` + +### 10. Shimmer Sweep + +A WAAPI text-animator runtime preset: soft left-to-center shimmer for continuous marketing motion. The CLI pulls the shared `text-animator` runtime (and CSS) used by other text presets—budget that shared dependency once, reuse across the category. Continuous loops still need a reduced-motion story in your app shell. Not the Motion package—WAAPI. + +[Shimmer Sweep docs](https://animata.design/docs/text/shimmer-sweep) + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/shimmer-sweep.json +``` + +Want more than these ten? The full text hub has the broader preset set (kinetic builds, shared-axis slides, underlines, counters, and more): **[Browse all text effects →](https://animata.design/docs/text)** + +## Install + +1. **Setup once:** [animata.design/docs/setup](https://animata.design/docs/setup) — Tailwind, `cn` (`clsx` + `tailwind-merge`), Motion optional when a component needs it. +2. **Registry (example):** + +```bash +pnpm dlx shadcn@latest add https://animata.design/r/text/typing-text.json +``` + +3. **Manual path:** files land under `components/animata/text/` (or the path your docs paste block uses). Own the source—edit timing, tokens, and copy. +4. **CSS extras:** some effects need global `@keyframes` / `@theme` (Wave Reveal, Text Flip-style keyframes). Others ship co-located CSS (Glitch Text; `text-animator.css` for WAAPI presets). Follow the component docs—do not assume every text file is Tailwind-only. + +Browse the catalog anytime: [animata.design/components](https://animata.design/components). Agents can also use the agent catalog linked above. + +## Motion vs CSS — when you need which + +Pick the lightest stack that does the job. + +CSS keyframes and Tailwind transitions are enough for flips, gradient clips, many reveals, glitch layers, and hover splits. Prefer them when the effect is decorative, local, and does not need scroll progress or layout measurement. + +WAAPI (`text-animator`) powers a family of Animata text presets—shared runtime, compositor-friendly loops, no Motion package. Useful for marketing cycles; still treat infinite loops as a reduced-motion concern. + +Reach for Motion (`motion/react`) when you need scroll-linked values, continuous path motion, or sequenced layout that CSS cannot express cleanly. Scroll Reveal and Circular Text are the clear examples in this list. + +Motion or GSAP for custom SplitText remains the right tool when you are authoring bespoke letter timelines, scrubbed pin scenes, or Club-level SplitText pipelines. Kits give you product-ready presets; they do not replace a custom Motion or GSAP SplitText build when that is the actual job. See [Motion's React Split Text example](https://motion.dev/examples/react-split-text) when you outgrow presets. + +Animata's posture matches the rest of the product kit: Motion optional for complex pieces—not "zero Motion forever," and not Motion for every label. + +## Accessibility and reduced motion + +Animated text fails quietly when decorative layers steal the accessible name, hover is the only affordance, or loops ignore `prefers-reduced-motion`. + +Wave Reveal keeps a full `sr-only` string beside animated glyphs—screen readers get the sentence once. Split Text marks decorative letter wrappers `aria-hidden` and uses an invisible sizing layer; still plan a non-hover static state for touch. Glitch Text exposes the base layer to assistive tech, hides RGB ghosts with `aria-hidden`, and disables ghost animation under `prefers-reduced-motion: reduce`. + +Hover-only effects (Split Text and similar) fail on touch—do not ship them as the only presentation of critical copy. Continuous loops (Circular Text, Shimmer Sweep, cycling flips) need reduced-motion fallbacks: pause, static first phrase, or CSS `animation: none` / Motion `useReducedMotion` in your shell. + +Accessibility is part of the paste surface—not a later ticket. + +## How this compares + +Short takes. These kits are strong; pick by surface. + +### Aceternity — cinematic text + +[Aceternity UI](https://ui.aceternity.com/components) remains a strong cinematic landing kit: heroes, shaders, and high-impact Motion text like [Text Generate Effect](https://ui.aceternity.com/components/text-generate-effect). Reach for it when the job is first-impression marketing motion—Animata text is aimed at a different surface. After paste, accessibility and bundle weight are yours to audit—same as any marketing kit. + +### Magic UI — TextAnimate + +[Magic UI TextAnimate](https://magicui.design/docs/components/text-animate) is polished Design Engineer marketing motion next to shadcn—blur/slide/scale presets, custom Motion variants, and an `accessible` screen-reader label option. Strong for landing micro-interactions; still verify reduced-motion and product-chrome fit the same way you would for any Motion-forward marketing component. + +### React Bits — kinetic gallery + +[React Bits](https://reactbits.dev) keeps expanding animated React experiments and production-leaning pieces. Worth browsing when you want kinetic variety beyond a single kit's opinion. + +### shadcn.io/text — freemium gallery + +[shadcn.io/text](https://www.shadcn.io/text) is a freemium text-effect gallery in the broader shadcn orbit. Useful for exploration; check license/tier per component before you treat it like an MIT copy-own library. + +### Cult UI + Motiq + +[Cult UI](https://cult-ui.com) sits in adjacent design-system craft. [Motiq](https://motiq.dev) belongs in the accessibility / reduced-motion conversation for motion-minded teams. Neither replaces a full text-preset catalog, but both are fair neighbors on a shortlist. + +### Motion / GSAP — build your own + +When presets are not enough, build: [Motion SplitText](https://motion.dev/examples/react-split-text) or GSAP SplitText for custom letter choreography. Kits accelerate shipping; engines win for bespoke timelines. + +## When not to use these Animata picks + +You need Aceternity-scale cinematic heroes / shaders? Use Aceternity (and similar kits) for that surface; Animata text is product-ready polish, not a full cinematic landing catalog. You specifically want Magic's TextAnimate catalog next to shadcn marketing? Use Magic for that Design Engineer marketing lane; mix kits by surface if needed. + +Every effect must be zero-Motion and zero-WAAPI? Scroll Reveal, Circular Text, and WAAPI presets will not fit that constraint—pick CSS-only entries or hand-roll. Critical copy depends on hover? Split-style drama is decorative; keep readable static text for touch and assistive tech. You need a full form/data primitive system? Start with [shadcn/ui](https://ui.shadcn.com/), then layer text motion where it helps. + +## FAQ + +### Are Animata text effects free / MIT? + +Yes. Animata's core library is **MIT, free forever**—**155+** components on the homepage copy-own model ([animata.design](https://animata.design/)). No paid tier required for these text presets. Treat sitewide counts as verify-at-publish (homepage versus agent-catalog drift noted above). + +### Do I need Motion for every text effect? + +No. Typing Text, Wave Reveal, Text Flip, Animated Gradient Text, Split Text, Glitch Text, Gibberish Text, and Shimmer Sweep (WAAPI) do not require the Motion package. **Scroll Reveal** and **Circular Text** do. Install Motion when the docs say so. + +### Does this work with Next.js and Vite? + +Yes. Animata targets modern React stacks including Next.js, Vite, Remix, Astro, Gatsby, and TanStack—see [setup](https://animata.design/docs/setup). Client components (`"use client"`) apply where hooks or browser APIs are used. + +### Can I build a typewriter in React with Tailwind without a package? + +Yes—and that is exactly [Typing Text](https://animata.design/docs/text/typing-text): React state/effects + Tailwind, registry or paste, no Motion. You own the file after install. + +### Split text without GSAP Club—GSAP vs Motion vs a kit component? + +**Kit component:** Animata [Split Text](https://animata.design/docs/text/split-text) for hover split drama you own (pointer-dependent; accessibility pattern documented). **Motion:** use [Motion's SplitText example](https://motion.dev/examples/react-split-text) when you need custom letter timelines without GSAP Club. **GSAP SplitText:** still excellent for scrubbed, Club-level letter pipelines—reach for it when the creative brief needs that engine, not when a kit preset already covers the job. + +### How should I handle reduced motion? + +Honor `prefers-reduced-motion`. Prefer components that already wire fallbacks (Glitch Text CSS; Wave Reveal's `sr-only` string). For continuous loops (Circular Text, Shimmer Sweep, cycling flips), pause or render static copy. Hover-only splits need a non-hover presentation. + +## Next steps + +1. [Text category overview](https://animata.design/docs/text) — full preset list (WAAPI called out for many presets). +2. [Setup](https://animata.design/docs/setup) — Tailwind, `cn`, Motion optional. +3. [Animated React buttons guide](https://animata.design/docs/guides/animated-react-buttons) — same copy-own pattern for CTAs. +4. [Browse all components](https://animata.design/components) — buttons, cards, scroll, text, and more. +5. [Aceternity UI vs Magic UI vs Animata](/blog/aceternity-ui-vs-magic-ui-vs-animata) — when to use each kit by surface. + +Ship one high-traffic headline first, verify reduced-motion and assistive tech, then expand. Own the files after install. diff --git a/hooks/use-exit-intent.ts b/hooks/use-exit-intent.ts index bf2d4124..2aa42bfe 100644 --- a/hooks/use-exit-intent.ts +++ b/hooks/use-exit-intent.ts @@ -3,6 +3,10 @@ import { useCallback, useEffect, useRef, useState } from "react"; const STORAGE_KEY = "animata-exit-shown"; +const DESKTOP_DELAY_MS = 5_000; +const MOBILE_TIME_MS = 25_000; +const MOBILE_SCROLL_RATIO = 0.45; +const MOBILE_MQ = "(max-width: 767px)"; export default function useExitIntent() { const [showModal, setShowModal] = useState(false); @@ -10,24 +14,45 @@ export default function useExitIntent() { const show = useCallback(() => { if (shown.current) return; - if (sessionStorage.getItem(STORAGE_KEY)) return; + if (typeof sessionStorage !== "undefined" && sessionStorage.getItem(STORAGE_KEY)) return; shown.current = true; sessionStorage.setItem(STORAGE_KEY, "1"); setShowModal(true); }, []); useEffect(() => { + let desktopReady = false; + const isMobile = () => window.matchMedia(MOBILE_MQ).matches; + const onMouseLeave = (e: MouseEvent) => { + if (!desktopReady || isMobile()) return; if (e.clientY <= 0) show(); }; - const timer = setTimeout(() => { - document.addEventListener("mouseleave", onMouseLeave); - }, 5000); + const onScroll = () => { + if (!isMobile()) return; + const doc = document.documentElement; + const scrollable = doc.scrollHeight - window.innerHeight; + if (scrollable <= 0) return; + if (window.scrollY / scrollable >= MOBILE_SCROLL_RATIO) show(); + }; + + document.addEventListener("mouseleave", onMouseLeave); + window.addEventListener("scroll", onScroll, { passive: true }); + + const desktopTimer = setTimeout(() => { + desktopReady = true; + }, DESKTOP_DELAY_MS); + + const mobileTimer = setTimeout(() => { + if (isMobile()) show(); + }, MOBILE_TIME_MS); return () => { - clearTimeout(timer); + clearTimeout(desktopTimer); + clearTimeout(mobileTimer); document.removeEventListener("mouseleave", onMouseLeave); + window.removeEventListener("scroll", onScroll); }; }, [show]); diff --git a/hooks/use-newsletter-subscription.ts b/hooks/use-newsletter-subscription.ts index 40e46b51..c013c686 100644 --- a/hooks/use-newsletter-subscription.ts +++ b/hooks/use-newsletter-subscription.ts @@ -1,10 +1,12 @@ "use client"; import { useState } from "react"; +import { trackEvent } from "@/lib/events"; + const plunkApiUrl = "https://api.useplunk.com/v1/track"; const plunkApiKey = process.env.NEXT_PUBLIC_PLUNK_API_KEY; -export default function useNewsletterSubscription() { +export default function useNewsletterSubscription(source = "unknown") { const initialState = { email: "", isLoading: false, @@ -42,6 +44,7 @@ export default function useNewsletterSubscription() { subscribed: true, data: { project_id: "animata", + source, }, }), }; @@ -50,7 +53,10 @@ export default function useNewsletterSubscription() { const response = await fetch(plunkApiUrl, options); if (response.status >= 200 && response.status < 300) { - // Email added successfully + trackEvent({ + name: "newsletter_subscribe", + properties: { source }, + }); setState({ ...initialState, isLoading: false, @@ -60,7 +66,6 @@ export default function useNewsletterSubscription() { } if (response.status === 409) { - // Already subscribed setState({ ...initialState, error: "You are already subscribed!", @@ -68,7 +73,6 @@ export default function useNewsletterSubscription() { return; } - // Other errors const errorData = await response.json(); setState({ ...initialState, diff --git a/lib/brand-font.ts b/lib/brand-font.ts index 84299583..bcb0b4fe 100644 --- a/lib/brand-font.ts +++ b/lib/brand-font.ts @@ -8,6 +8,4 @@ export const brandFont = Outfit({ display: "swap", }); -/** Crisp geometric wordmark beside the logo mark */ -export const brandLabelClassName = - "font-(family-name:--font-brand) text-[1em] font-semibold lowercase tracking-[-0.045em]"; +export { brandLabelClassName } from "@/lib/brand-label"; diff --git a/lib/brand-label.ts b/lib/brand-label.ts new file mode 100644 index 00000000..0d4eed7b --- /dev/null +++ b/lib/brand-label.ts @@ -0,0 +1,5 @@ +/** Crisp geometric wordmark beside the logo mark. + * Client-safe: no next/font import (that stays in brand-font.ts / layout). + */ +export const brandLabelClassName = + "font-(family-name:--font-brand) text-[1em] font-semibold lowercase tracking-[-0.045em]"; diff --git a/lib/events.ts b/lib/events.ts index 76f35491..e9f4934c 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -4,7 +4,14 @@ import { z } from "zod"; import { config } from "@/config"; const eventSchema = z.object({ - name: z.enum(["copy_npm_command", "copy_touch_command", "copy_usage_code", "copy_source_code"]), + name: z.enum([ + "copy_npm_command", + "copy_touch_command", + "copy_usage_code", + "copy_source_code", + "newsletter_subscribe", + "github_star_click", + ]), properties: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(), }); diff --git a/lib/metadata.ts b/lib/metadata.ts index 680fe0ca..c7d667ee 100644 --- a/lib/metadata.ts +++ b/lib/metadata.ts @@ -241,21 +241,36 @@ function buildBreadcrumbJsonLd(doc: PublishedDoc) { } function buildGuideItemListJsonLd(doc: PublishedDoc, description: string) { - if (doc.slugAsParams !== "guides/animated-react-buttons") { + const guideLists: Record> = { + "guides/animated-react-buttons": [ + { name: "AI Button", url: "/docs/button/ai-button" }, + { name: "Duolingo Button", url: "/docs/button/duolingo" }, + { name: "Ripple Button", url: "/docs/button/ripple-button" }, + { name: "Shining Button", url: "/docs/button/shining-button" }, + { name: "Swipe Button", url: "/docs/button/swipe-button" }, + { name: "Status Button", url: "/docs/button/status-button" }, + { name: "Animated Follow Button", url: "/docs/button/animated-follow-button" }, + { name: "Get Started Button", url: "/docs/button/get-started-button" }, + ], + "guides/animated-react-text-effects": [ + { name: "Typing Text", url: "/docs/text/typing-text" }, + { name: "Wave Reveal", url: "/docs/text/wave-reveal" }, + { name: "Text Flip", url: "/docs/text/text-flip" }, + { name: "Animated Gradient Text", url: "/docs/text/animated-gradient-text" }, + { name: "Split Text", url: "/docs/text/split-text" }, + { name: "Scroll Reveal", url: "/docs/text/scroll-reveal" }, + { name: "Circular Text", url: "/docs/text/circular-text" }, + { name: "Glitch Text", url: "/docs/text/glitch-text" }, + { name: "Gibberish Text", url: "/docs/text/gibberish-text" }, + { name: "Shimmer Sweep", url: "/docs/text/shimmer-sweep" }, + ], + }; + + const items = guideLists[doc.slugAsParams]; + if (!items) { return null; } - const items = [ - { name: "AI Button", url: "/docs/button/ai-button" }, - { name: "Duolingo Button", url: "/docs/button/duolingo" }, - { name: "Ripple Button", url: "/docs/button/ripple-button" }, - { name: "Shining Button", url: "/docs/button/shining-button" }, - { name: "Swipe Button", url: "/docs/button/swipe-button" }, - { name: "Status Button", url: "/docs/button/status-button" }, - { name: "Animated Follow Button", url: "/docs/button/animated-follow-button" }, - { name: "Get Started Button", url: "/docs/button/get-started-button" }, - ]; - return { "@context": "https://schema.org", "@type": "ItemList", @@ -351,10 +366,83 @@ export function buildBlogMetadata(blog: PublishedBlog): Metadata { description, path: blog.slug, type: "article", - keywords: [blog.title, ...(blog.labels ?? [])], + keywords: [ + blog.title, + ...(blog.labels ?? []), + "animated React components", + "Tailwind CSS", + ], }); } +function buildBlogAuthorJsonLd(blog: PublishedBlog) { + if (!blog.author || blog.author === "AnimataDesign") { + return { + "@type": "Organization", + name: BRAND_SUFFIX, + url: siteConfig.url, + }; + } + + return { + "@type": "Person", + name: blog.author, + url: `https://twitter.com/${blog.author}`, + }; +} + +function buildBlogFaqJsonLd(blog: PublishedBlog) { + if (blog.slugAsParams !== "aceternity-ui-vs-magic-ui-vs-animata") { + return null; + } + + const faqs = [ + { + question: "What is the difference between Aceternity UI, Magic UI, and Animata?", + answer: + "Aceternity UI is strongest for cinematic landing pages. Magic UI is strongest for polished marketing micro-interactions next to shadcn/ui. Animata is strongest for product UI polish you own under MIT, with accessibility and reduced-motion patterns included before paste.", + }, + { + question: "Is Animata a free open-source Aceternity alternative?", + answer: + "For product UI animated components you own, yes—Animata is free MIT open source. It is not a 1:1 substitute for Aceternity's cinematic landing catalog. Many teams use both.", + }, + { + question: "When should I use Aceternity UI, Magic UI, or Animata?", + answer: + "Use Aceternity for marketing heroes and high-impact landing sections. Use Magic for marketing micro-interactions beside shadcn. Use Animata for buttons, cards, tabs, scroll, and text inside the product.", + }, + { + question: "Is Animata free forever?", + answer: + "Yes. Animata's animated React components are free, open source, and MIT-licensed for the core library.", + }, + { + question: "Do I need Motion for Animata?", + answer: + "Not for everything. Motion is optionally required for some complex animations. Check each component's docs before assuming Motion is required.", + }, + { + question: "Magic UI vs Animata for app UI?", + answer: + "For in-app product chrome with ownership and accessibility defaults, prefer Animata. For marketing sections and Design Engineer micro-interactions beside shadcn, prefer Magic. They can coexist.", + }, + ]; + + return { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: faqs.map((faq) => ({ + "@type": "Question", + name: faq.question, + acceptedAnswer: { + "@type": "Answer", + text: faq.answer, + }, + })), + }; +} + export function buildBlogJsonLd(blog: PublishedBlog, description: string) { const headline = blog.seoTitle ?? blog.title; @@ -369,17 +457,7 @@ export function buildBlogJsonLd(blog: PublishedBlog, description: string) { datePublished: blog.date, dateModified: blog.dateModified ?? blog.date, image: siteConfig.ogImage, - author: blog.author - ? { - "@type": "Person", - name: blog.author, - url: `https://twitter.com/${blog.author}`, - } - : { - "@type": "Organization", - name: BRAND_SUFFIX, - url: siteConfig.url, - }, + author: buildBlogAuthorJsonLd(blog), publisher: { "@type": "Organization", name: BRAND_SUFFIX, @@ -389,6 +467,11 @@ export function buildBlogJsonLd(blog: PublishedBlog, description: string) { url: siteConfig.ogImage, }, }, + isAccessibleForFree: true, + mainEntityOfPage: { + "@type": "WebPage", + "@id": absoluteUrl(blog.slug), + }, }; const breadcrumb = { @@ -401,7 +484,8 @@ export function buildBlogJsonLd(blog: PublishedBlog, description: string) { ], }; - return [article, breadcrumb]; + const faq = buildBlogFaqJsonLd(blog); + return faq ? [article, breadcrumb, faq] : [article, breadcrumb]; } export const homePageMetadata = createPageMetadata({ diff --git a/scripts/build-llms-txt.js b/scripts/build-llms-txt.js index 8f7b6de9..33563ee2 100644 --- a/scripts/build-llms-txt.js +++ b/scripts/build-llms-txt.js @@ -74,6 +74,12 @@ function buildIndex(byCategory) { "", `Install any component with: \`pnpm dlx shadcn@latest add ${SITE_URL}/r/{category}/{component}.json\``, "", + "## Guides & comparisons", + "", + `- [Top Animated React Buttons (2026)](${SITE_URL}/docs/guides/animated-react-buttons)`, + `- [Top Animated React Text Effects (2026)](${SITE_URL}/docs/guides/animated-react-text-effects)`, + `- [Aceternity UI vs Magic UI vs Animata](${SITE_URL}/blog/aceternity-ui-vs-magic-ui-vs-animata)`, + "", ]; for (const { slug, title } of CATEGORY_ORDER) {