# UI Beats: full documentation > Beautifully designed, animated React components built with React, TypeScript, Tailwind CSS and Motion. Copy, paste, or install with the shadcn CLI. This file contains every component in the library: description, props, example usage and complete source. Install any of them with: ```bash npx shadcn@latest add https://uibeats.com/r/.json ``` --- # Feature Grid The FeatureGrid block arranges a set of features into a bento layout, letting each one declare how many columns it spans so the arrangement itself ranks them. - **Category:** Block - **Docs:** https://uibeats.com/docs/block/feature-grid - **Registry:** https://uibeats.com/r/feature-grid.json - **License:** MIT - **npm dependencies:** `lucide-react` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/feature-grid.json ``` Or install the dependencies and copy the source below: ```bash npm install lucide-react ``` ## When to use For the section after the hero. The uneven spans are what rank your features, so give the one you lead with two columns and the rest one. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `features` | `Feature[]` | `-` | The features to lay out, in display order (required) | | `heading` | `string` | `-` | Section heading above the grid | | `description` | `string` | `-` | Supporting line under the heading | | `columns` | `number` | `3` | Columns from the sm breakpoint up. Below it every card is full width | | `Feature: title` | `string` | `-` | The feature's name | | `Feature: description` | `string` | `-` | One or two lines on what it does | | `Feature: icon` | `LucideIcon` | `-` | Optional icon shown above the title | | `Feature: colSpan` | `number` | `1` | Columns this feature spans, from sm up | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx "use client"; import { Boxes, Palette, Sparkles, Zap } from "lucide-react"; import { FeatureGrid } from "@/components/demo/block/feature-grid"; const FEATURES = [ { title: "Copy, paste, own it", description: "Every component lands in your repo as plain source you can edit.", icon: Boxes, colSpan: 2, }, { title: "Motion included", description: "Animation is part of the component, not a wrapper added later.", icon: Sparkles, colSpan: 1, }, { title: "Themed by default", description: "Colours come from your own tokens.", icon: Palette, colSpan: 1, }, { title: "Documented properly", description: "Every prop has a table, a playground, and a reason to exist.", icon: Zap, colSpan: 2, }, ]; const FeatureGridUsage = () => { return ( ); }; export default FeatureGridUsage; ``` ## Source Save as `components/ui/feature-grid.tsx`. ```tsx "use client"; import type { LucideIcon } from "lucide-react"; import { BentoGrid, BentoCard } from "@/components/demo/component/bento-grid"; import { cn } from "@/lib/utils"; export interface Feature { title: string; description: string; icon?: LucideIcon; /** Columns this feature spans, from the sm breakpoint up. */ colSpan?: number; } interface FeatureGridProps { heading?: string; description?: string; features: Feature[]; /** Columns from the sm breakpoint up. */ columns?: number; className?: string; } /** * A feature section laid out as a bento grid. * * Each feature declares its own `colSpan`, so the arrangement ranks them: the * capability you lead with takes two columns and the supporting ones take one. * That only works if you mean the ranking. Give every feature the same span and * you have an ordinary grid with extra steps, and an even grid says it more * clearly. */ export function FeatureGrid({ heading, description, features, columns = 3, className = "", }: FeatureGridProps) { return (
{heading || description ? (
{heading ? (

{heading}

) : null} {description ? (

{description}

) : null}
) : null} {features.map(({ title, description: body, icon: Icon, colSpan }) => ( {Icon ? ( ) : null}

{title}

{body}

))}
); } export default FeatureGrid; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Hero The Hero block is a complete landing page opening: eyebrow, headline, description, two calls to action and a row of faces for social proof, over a field of falling meteors. Installing it installs the three components it is built from. - **Category:** Block - **Docs:** https://uibeats.com/docs/block/hero - **Registry:** https://uibeats.com/r/hero.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/hero.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use The opening section of a marketing page. It ships the whole arrangement, so use it when centred copy with two actions is the layout you want. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `title` | `React.ReactNode` | `-` | The headline. Takes nodes, so part of it can be styled or animated (required) | | `eyebrow` | `string` | `-` | Small line above the headline, such as a release note | | `description` | `string` | `-` | Supporting line under the headline | | `primaryAction` | `{ label: string; href: string }` | `-` | The main call to action, rendered as a Shimmer Button | | `secondaryAction` | `{ label: string; href: string }` | `-` | A quieter second action beside the first | | `avatars` | `AvatarStackItem[]` | `-` | Faces for the social proof row. Omit it and the row is not rendered | | `proof` | `string` | `-` | The line beside those faces, e.g. 'Joined by 2,400 teams' | | `showMeteors` | `boolean` | `true` | Streaks behind the content. Set false for a plain surface | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx "use client"; import { Hero } from "@/components/demo/block/hero"; const TEAM = [ { name: "Ava Chen" }, { name: "Marcus Hale" }, { name: "Priya Nair" }, { name: "Diego Ramos" }, { name: "Yuki Tanaka" }, { name: "Noor Haddad" }, ]; const HeroUsage = () => { return ( Ship the section, not the setup } description="Install one block and it brings every component it uses along with it." primaryAction={{ label: "Get started", href: "#" }} secondaryAction={{ label: "Read the docs", href: "#" }} avatars={TEAM} proof="Joined by 2,400 teams" /> ); }; export default HeroUsage; ``` ## Source Save as `components/ui/hero.tsx`. ```tsx "use client"; import type { ReactNode } from "react"; import { motion, useReducedMotion } from "motion/react"; import { Meteors } from "@/components/demo/background/meteors"; import { ShimmerButton } from "@/components/demo/button/shimmer-button"; import { AvatarStack, type AvatarStackItem, } from "@/components/demo/component/avatar-stack"; import { cn } from "@/lib/utils"; interface HeroAction { label: string; href: string; } interface HeroProps { /** Small line above the headline, e.g. a release note or a category. */ eyebrow?: string; title: ReactNode; description?: string; primaryAction?: HeroAction; secondaryAction?: HeroAction; /** Faces for the social proof row. Omit it and the row is not rendered. */ avatars?: AvatarStackItem[]; /** The line beside those faces, e.g. "Joined by 2,400 teams". */ proof?: string; /** Streaks behind the content. Set false for a plain surface. */ showMeteors?: boolean; className?: string; } /** * A landing page hero, assembled from three components in this library. * * Installing it installs Meteors, Shimmer Button and Avatar Stack with it, so * the section arrives working rather than as a file full of imports you have * to go and satisfy one by one. Everything it renders is a prop, and the file * itself is yours the moment the CLI writes it, so the parts you want to * rearrange are ordinary JSX rather than configuration. * * Links are plain anchors on purpose. A block that reached for `next/link` * would only work in a Next.js project, and there is nothing else in here that * cares which framework is rendering it. */ export function Hero({ eyebrow, title, description, primaryAction, secondaryAction, avatars, proof, showMeteors = true, className = "", }: HeroProps) { const prefersReducedMotion = useReducedMotion(); const rise = prefersReducedMotion ? {} : { initial: { opacity: 0, y: 16 }, animate: { opacity: 1, y: 0 }, }; return (
{showMeteors ? ( ) : null}
{eyebrow ? ( {eyebrow} ) : null} {title} {description ? ( {description} ) : null} {primaryAction || secondaryAction ? ( {primaryAction ? ( {primaryAction.label} ) : null} {secondaryAction ? ( {secondaryAction.label} ) : null} ) : null} {avatars?.length ? ( {proof ? ( {proof} ) : null} ) : null}
); } export default Hero; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Pricing The Pricing block lays out a row of plans and traces the recommended one with a travelling Border Beam, so the tier you want chosen is the one that moves rather than the one wearing another badge. - **Category:** Block - **Docs:** https://uibeats.com/docs/block/pricing - **Registry:** https://uibeats.com/r/pricing.json - **License:** MIT - **npm dependencies:** `lucide-react` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/pricing.json ``` Or install the dependencies and copy the source below: ```bash npm install lucide-react ``` ## When to use For a pricing section where one plan is the one you want chosen. Mark exactly one tier as featured; if every plan is equal, a plain grid says it better. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `tiers` | `PricingTier[]` | `-` | The plans, in display order (required) | | `heading` | `string` | `-` | Section heading above the tiers | | `description` | `string` | `-` | Supporting line under the heading | | `PricingTier: name` | `string` | `-` | Plan name, e.g. Pro | | `PricingTier: price` | `string` | `-` | Headline price, as text so Free and $19 both work | | `PricingTier: period` | `string` | `-` | Shown after the price, e.g. /month | | `PricingTier: features` | `string[]` | `-` | Ticked list of what the plan includes | | `PricingTier: action` | `{ label: string; href: string }` | `-` | The plan's call to action | | `PricingTier: featured` | `boolean` | `false` | Traces the card with a Border Beam and promotes its button. Only the first tier claiming it is honoured | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx "use client"; import { Pricing } from "@/components/demo/block/pricing"; const TIERS = [ { name: "Hobby", price: "Free", description: "Everything you need to start.", features: ["All 40 components", "MIT licensed", "Community support"], action: { label: "Get started", href: "#" }, }, { name: "Pro", price: "$19", period: "/month", description: "For teams shipping regularly.", features: ["Everything in Hobby", "Private registry", "Priority support"], action: { label: "Start free trial", href: "#" }, featured: true, }, { name: "Team", price: "$49", period: "/month", description: "For a whole design system.", features: ["Everything in Pro", "Shared themes", "Onboarding call"], action: { label: "Contact sales", href: "#" }, }, ]; const PricingUsage = () => { return ( ); }; export default PricingUsage; ``` ## Source Save as `components/ui/pricing.tsx`. ```tsx "use client"; import { Check } from "lucide-react"; import { BorderBeam } from "@/components/demo/component/border-beam"; import { ShimmerButton } from "@/components/demo/button/shimmer-button"; import { cn } from "@/lib/utils"; export interface PricingTier { name: string; price: string; /** Shown after the price, e.g. "/month". */ period?: string; description?: string; features: string[]; action: { label: string; href: string }; /** Traces the card with a Border Beam and leads with the primary button. */ featured?: boolean; } interface PricingProps { heading?: string; description?: string; tiers: PricingTier[]; className?: string; } /** * A pricing section, with the recommended tier traced by a Border Beam. * * The beam is the whole reason this is a block rather than three cards in a * grid: singling out a plan usually means a coloured border and a "Most * popular" pill, both of which are static. A light that travels the recommended * card's edge draws the eye without adding another badge to read. * * Exactly one tier should carry `featured`. Two beams racing each other is not * emphasis, it is decoration, so the component only reads the first. */ export function Pricing({ heading, description, tiers, className = "", }: PricingProps) { // Emphasis is a comparison, so only the first claim to it is honoured. const featuredIndex = tiers.findIndex((tier) => tier.featured); return (
{heading || description ? (
{heading ? (

{heading}

) : null} {description ? (

{description}

) : null}
) : null}
{tiers.map((tier, index) => { const isFeatured = index === featuredIndex; const card = (

{tier.name}

{tier.price} {tier.period ? ( {tier.period} ) : null}

{tier.description ? (

{tier.description}

) : null}
    {tier.features.map((feature) => (
  • {feature}
  • ))}
{isFeatured ? ( {tier.action.label} ) : ( {tier.action.label} )}
); return isFeatured ? ( {card} ) : (
{card}
); })}
); } export default Pricing; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Animated List The AnimatedList component streams its children in one at a time, springing each new entry into place and pushing the older ones down until they fall out of the bottom of the list. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/animated-list - **Registry:** https://uibeats.com/r/animated-list.json - **License:** MIT - **npm dependencies:** `lucide-react`, `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/animated-list.json ``` Or install the dependencies and copy the source below: ```bash npm install lucide-react motion ``` ## When to use For a product that produces a feed: notifications, orders, deploys, alerts. A still screenshot cannot show that things keep arriving. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | One entry per child, emitted in order | | `delay` | `number` | `1400` | Milliseconds between entries arriving | | `max` | `number` | `4` | How many entries stay on screen before the oldest is dropped | | `loop` | `boolean` | `true` | Start over once the last child has been emitted | | `live` | `boolean` | `false` | Announce arrivals to screen readers, for genuinely live content | | `className` | `string` | `''` | Additional CSS classes applied to the list | ## Example usage ```tsx "use client"; import { CreditCard, GitPullRequest, Star, UserPlus } from "lucide-react"; import { AnimatedList } from "@/components/demo/animation/animated-list"; const events = [ { icon: Star, title: "New star on ui-beats", meta: "just now", tone: "text-amber-500", }, { icon: CreditCard, title: "Payment received · $49.00", meta: "1m ago", tone: "text-emerald-500", }, { icon: UserPlus, title: "Priya joined your workspace", meta: "2m ago", tone: "text-brand", }, { icon: GitPullRequest, title: "PR #218 ready for review", meta: "4m ago", tone: "text-sky-500", }, ]; const AnimatedListUsage = () => { return (
{events.map(({ icon: Icon, title, meta, tone }) => (

{title}

{meta}

))}
); }; export default AnimatedListUsage; ``` ## Source Save as `components/ui/animated-list.tsx`. ```tsx "use client"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { Children, useEffect, useState, type ReactNode } from "react"; import { cn } from "@/lib/utils"; interface AnimatedListProps { /** One entry per child, emitted in order. */ children: ReactNode; /** Milliseconds between entries arriving. */ delay?: number; /** How many entries stay on screen before the oldest is dropped. */ max?: number; /** Start over once the last child has been emitted. */ loop?: boolean; /** Announce arrivals to screen readers, for genuinely live content. */ live?: boolean; className?: string; } /** * A feed where entries arrive one at a time, pushing the ones already there * down and out of the bottom of the list. * * Only `max` entries are mounted at once, so a feed that has been running for * an hour costs exactly what it did in its first second. The shuffle is a * layout animation rather than hand-computed offsets: each surviving entry * animates from wherever it was to wherever the new arrival pushed it. * * Nothing here is real notification content; if you wire it to some, turn on * `live` so the arrivals are announced rather than appearing silently. */ export function AnimatedList({ children, delay = 1400, max = 4, loop = true, live = false, className = "", }: AnimatedListProps) { const entries = Children.toArray(children); const prefersReducedMotion = useReducedMotion(); // Total emitted so far. The newest entry is always `emitted - 1`, which // makes it its own stable key however many laps the list has run. const [emitted, setEmitted] = useState(1); const finished = !loop && emitted >= entries.length; useEffect(() => { if (entries.length === 0 || finished || prefersReducedMotion) return; const timer = setTimeout(() => setEmitted((count) => count + 1), delay); return () => clearTimeout(timer); }, [emitted, delay, entries.length, finished, prefersReducedMotion]); if (entries.length === 0) return null; // Reduced motion gets the finished list, not a silent animation: the point // of the component is the arrival, and there is no quiet way to show it. const visibleEntries = prefersReducedMotion ? entries.slice(0, max).map((node, position) => ({ key: position, node })) : Array.from( { length: Math.min(emitted, max, entries.length) }, (_, position) => { const sequence = emitted - 1 - position; return { key: sequence, node: entries[sequence % entries.length] }; }, ); return (
{visibleEntries.map(({ key, node }, position) => ( {node} ))}
); } export default AnimatedList; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Bounce The Bounce component adds a playful, elastic animation to its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/bounce - **Registry:** https://uibeats.com/r/bounce.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/bounce.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Reach for Bounce when an element should feel physical on arrival: a price, a stat, a confirmation. The spring overshoot pulls the eye, so use it on one or two elements per screen. Across a whole list the repeated overshoot just reads as noise. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be animated (required) | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `duration` | `number` | `0.5` | Duration of the animation (in seconds) | | `bounceHeight` | `number` | `20` | Height of the bounce effect (in pixels) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import Bounce from "@/components/demo/animation/bounce"; const BounceUsage = () => { return (
This content will bounce when visible
); }; export default BounceUsage; ``` ## Source Save as `components/ui/bounce.tsx`. ```tsx "use client"; import React from "react"; import { motion, useInView, useReducedMotion, type Variants, } from "motion/react"; interface BounceProps { children: React.ReactNode; delay?: number; duration?: number; bounceHeight?: number; className?: string; once?: boolean; } const Bounce: React.FC = ({ children, delay = 0, duration = 0.5, bounceHeight = 20, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); /* * Under reduced motion the element is settled from the first frame. * Dropping only the transition would not do: the `hidden` variant stays * applied until the element scrolls into view, so the content would sit * invisible and offset instead of simply not bouncing. */ const variants: Variants = prefersReducedMotion ? { hidden: { opacity: 1, y: 0 }, visible: { opacity: 1, y: 0 } } : { hidden: { opacity: 0, y: bounceHeight }, visible: { opacity: 1, y: 0, transition: { delay, duration, type: "spring", stiffness: 200, damping: 10, }, }, }; return ( {children} ); }; export default Bounce; ``` ## Credits Written by [Michael](https://github.com/lappemic). --- # Fade In The FadeIn component creates a smooth fade-in animation for its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/fade-in - **Registry:** https://uibeats.com/r/fade-in.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/fade-in.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use The safest entrance animation, and the right default when you are unsure. Use it for body copy, images and anything below the fold that should feel considered rather than demand attention. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be faded in (required) | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `duration` | `number` | `0.5` | Duration of the animation (in seconds) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import FadeIn from "@/components/demo/animation/fade-in"; const FadeInUsage = () => { return (

This content will fade in when it enters the viewport

); }; export default FadeInUsage; ``` ## Source Save as `components/ui/fade-in.tsx`. ```tsx "use client"; import React from "react"; import { motion, useInView, useReducedMotion, type Variants, } from "motion/react"; interface FadeInProps { children: React.ReactNode; delay?: number; duration?: number; className?: string; once?: boolean; } const FadeIn: React.FC = ({ children, delay = 0, duration = 0.5, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); /* * A fade moves nothing, so this is the one entrance that could arguably be * left running. It is switched off anyway for a duller reason: the fade is * gated on `useInView`, and a reader who has asked for no motion should not * have to scroll content into view before it becomes readable. */ const variants: Variants = prefersReducedMotion ? { hidden: { opacity: 1 }, visible: { opacity: 1 } } : { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { duration: duration, delay: delay, ease: "easeOut", }, }, }; return ( {children} ); }; export default FadeIn; ``` ## Credits Written by [Michael](https://github.com/lappemic). --- # Fade In Unblur The FadeInUnblur component creates a smooth fade-in while unblurring animation for its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/fade-in-unblur - **Registry:** https://uibeats.com/r/fade-in-unblur.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/fade-in-unblur.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use A fade with a defocus-to-focus feel, which buys a moment of anticipation. Good for hero imagery and product screenshots; too heavy for repeated list items, where the blur becomes tiring. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be faded in unblurred (required) | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `duration` | `number` | `0.5` | Duration of the animation (in seconds) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import FadeInUnblur from "@/components/demo/animation/fade-in-unblur"; const FadeInUnblurUsage = () => { return (

This content fades in while unblurring when it enters the viewport

); }; export default FadeInUnblurUsage; ``` ## Source Save as `components/ui/fade-in-unblur.tsx`. ```tsx "use client"; import React from "react"; import { motion, Variants, useInView, useReducedMotion } from "motion/react"; interface FadeInUnblurProps { children: React.ReactNode; delay?: number; duration?: number; className?: string; once?: boolean; } const FadeInUnblur: React.FC = ({ children, delay = 0, duration = 0.5, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); /* * The settled state keeps `scale: 1.2`, which is where the animation ends, * and matching it is the point. Reducing it to 1 here would render the * component at a different size for these users than for everyone else. */ const variants: Variants = prefersReducedMotion ? { hidden: { opacity: 1, scale: 1.2, filter: "blur(0px)" }, visible: { opacity: 1, scale: 1.2, filter: "blur(0px)" }, } : { hidden: { opacity: 0, scale: 1.1, filter: "blur(4px)", }, visible: { opacity: 1, scale: 1.2, filter: "blur(0px)", transition: { duration: duration, delay: delay, ease: [0.47, 0, 0.745, 0.715], }, }, }; return ( {children} ); }; export default FadeInUnblur; ``` ## Credits Written by [Divyaswor](https://github.com/divyaswormakai). --- # Rotate In The RotateIn component creates a smooth rotation animation for its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/rotate-in - **Registry:** https://uibeats.com/r/rotate-in.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/rotate-in.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Best on small, self-contained elements: icons, badges, logos, avatars. Rotating a block of text makes it briefly unreadable, so keep this for things that read as objects instead of as content. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be rotated in (required) | | `duration` | `number` | `0.5` | Duration of the animation (in seconds) | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `rotateFrom` | `number` | `90` | Initial rotation angle (in degrees) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import RotateIn from "@/components/demo/animation/rotate-in"; const RotateInUsage = () => { return (
This content will rotate in when visible
); }; export default RotateInUsage; ``` ## Source Save as `components/ui/rotate-in.tsx`. ```tsx "use client"; import React from "react"; import { motion, useInView, useReducedMotion, type Variants, } from "motion/react"; interface RotateInProps { children: React.ReactNode; duration?: number; delay?: number; rotateFrom?: number; className?: string; once?: boolean; } const RotateIn: React.FC = ({ children, duration = 0.5, delay = 0, rotateFrom = 90, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); // A quarter turn is the most vestibular-provoking entrance in the set, so // this one matters most: settled and unrotated from the first frame. const variants: Variants = prefersReducedMotion ? { hidden: { rotate: 0, opacity: 1 }, visible: { rotate: 0, opacity: 1 } } : { hidden: { rotate: rotateFrom, opacity: 0 }, visible: { rotate: 0, opacity: 1, transition: { duration: duration, delay: delay, ease: [0.215, 0.61, 0.355, 1], }, }, }; return ( {children} ); }; export default RotateIn; ``` ## Credits Written by [Michael](https://github.com/lappemic). --- # Scale In The ScaleIn component creates a smooth scale-in animation for its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/scale-in - **Registry:** https://uibeats.com/r/scale-in.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/scale-in.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Use it when an element should feel like it is arriving out of the page instead of sliding onto it: modals, cards, images. Keep scaleFrom between 0.9 and 0.95. Anything lower reads as a zoom, not an entrance. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be scaled in (required) | | `duration` | `number` | `0.5` | Duration of the animation (in seconds) | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `scaleFrom` | `number` | `0.8` | Initial scale value (between 0 and 1) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import ScaleIn from "@/components/demo/animation/scale-in"; const ScaleInUsage = () => { return (
This content will scale in when visible
); }; export default ScaleInUsage; ``` ## Source Save as `components/ui/scale-in.tsx`. ```tsx "use client"; import React from "react"; import { motion, useInView, useReducedMotion, type Variants, } from "motion/react"; interface ScaleInProps { children: React.ReactNode; duration?: number; delay?: number; scaleFrom?: number; className?: string; once?: boolean; } const ScaleIn: React.FC = ({ children, duration = 0.5, delay = 0, scaleFrom = 0.8, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); // Settled at full size from the first frame, in view or not. const variants: Variants = prefersReducedMotion ? { hidden: { scale: 1, opacity: 1 }, visible: { scale: 1, opacity: 1 } } : { hidden: { scale: scaleFrom, opacity: 0 }, visible: { scale: 1, opacity: 1, transition: { duration: duration, delay: delay, ease: [0.215, 0.61, 0.355, 1], }, }, }; return ( {children} ); }; export default ScaleIn; ``` ## Credits Written by [Michael](https://github.com/lappemic). --- # Smooth Reveal The SmoothReveal component creates a smooth reveal animation for its children when they enter the viewport. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/smooth-reveal - **Registry:** https://uibeats.com/r/smooth-reveal.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/smooth-reveal.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use The workhorse for long scrolling pages. Match the direction to reading order: up for stacked sections, left or right for side-by-side columns. Keep distance under about 60px. Past that the movement reads as a jump instead of a reveal. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be revealed (required) | | `direction` | `'up' \| 'down' \| 'left' \| 'right'` | `'up'` | The direction of the reveal animation | | `delay` | `number` | `0` | Delay before the animation starts (in seconds) | | `duration` | `number` | `0.6` | Duration of the animation (in seconds) | | `distance` | `number` | `50` | Distance of the reveal animation (in pixels) | | `className` | `string` | `''` | Additional CSS classes for styling | | `once` | `boolean` | `true` | Whether to trigger the animation only once | ## Example usage ```tsx import React from "react"; import SmoothReveal from "@/components/demo/animation/smooth-reveal"; const SmoothRevealUsage = () => { return (

This content will smoothly reveal on scroll

); }; export default SmoothRevealUsage; ``` ## Source Save as `components/ui/smooth-reveal.tsx`. ```tsx "use client"; import React from "react"; import { cubicBezier, motion, useInView, useReducedMotion } from "motion/react"; type SmoothRevealProps = { children: React.ReactNode; direction?: "up" | "down" | "left" | "right"; delay?: number; duration?: number; distance?: number; className?: string; once?: boolean; }; const SmoothReveal: React.FC = ({ children, direction = "up", delay = 0, duration = 0.8, distance = 100, className = "", once = true, }) => { const ref = React.useRef(null); const isInView = useInView(ref, { once, amount: 0.1 }); const prefersReducedMotion = useReducedMotion(); const getDirectionalProps = () => { switch (direction) { case "down": return { y: -distance }; case "left": return { x: distance }; case "right": return { x: -distance }; case "up": default: return { y: distance }; } }; const customEasing = cubicBezier(0.2, 0.0, 0.0, 1.0); // 100px of travel by default: the largest displacement of any entrance // here, and the one most worth removing when asked to. const variants = prefersReducedMotion ? { hidden: { opacity: 1, x: 0, y: 0 }, visible: { opacity: 1, x: 0, y: 0 }, } : { hidden: { opacity: 0, ...getDirectionalProps(), }, visible: { opacity: 1, y: 0, x: 0, transition: { duration: duration, ease: customEasing, delay: delay, }, }, }; return ( {children} ); }; export default SmoothReveal; ``` --- # Stagger List The StaggerList component animates its children into view one after another when the list enters the viewport, without requiring any changes to the children themselves. - **Category:** Animation - **Docs:** https://uibeats.com/docs/animation/stagger-list - **Registry:** https://uibeats.com/r/stagger-list.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/stagger-list.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use For any group that would otherwise arrive as a single slab: feature bullets, pricing tiers, navigation items. The stagger implies sequence and gives the eye somewhere to start. Drop stagger below 0.08s once the list runs past six items, or the last row arrives noticeably late. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | Items to stagger; each direct child is wrapped (required) | | `stagger` | `number` | `0.08` | Seconds between each child's entrance | | `delay` | `number` | `0` | Seconds before the first child animates | | `duration` | `number` | `0.5` | Duration of each child's entrance, in seconds | | `distance` | `number` | `24` | Travel distance of the entrance, in pixels | | `direction` | `'up' \| 'down' \| 'left' \| 'right'` | `'up'` | Direction the children travel from | | `once` | `boolean` | `true` | Animate only the first time the list enters the viewport | | `className` | `string` | `''` | Additional CSS classes for the container | ## Example usage ```tsx "use client"; import { StaggerList } from "@/components/demo/animation/stagger-list"; const StaggerListUsage = () => { return ( {["Design", "Build", "Animate", "Ship"].map((label) => (
{label}
))}
); }; export default StaggerListUsage; ``` ## Source Save as `components/ui/stagger-list.tsx`. ```tsx "use client"; import { useRef, type ReactNode } from "react"; import { motion, useInView, useReducedMotion, type Variants, } from "motion/react"; interface StaggerListProps { children: ReactNode; /** Seconds between each child's entrance. */ stagger?: number; /** Seconds before the first child animates. */ delay?: number; /** Duration of each child's entrance, in seconds. */ duration?: number; /** Travel distance of the entrance, in pixels. */ distance?: number; direction?: "up" | "down" | "left" | "right"; /** Animate only the first time the list enters the viewport. */ once?: boolean; className?: string; } const offsetFor = (direction: StaggerListProps["direction"], d: number) => { switch (direction) { case "down": return { y: -d }; case "left": return { x: d }; case "right": return { x: -d }; default: return { y: d }; } }; /** * Animates its children into view one after another. Each direct child is * wrapped, so any markup can be staggered without changing the child itself. */ export function StaggerList({ children, stagger = 0.08, delay = 0, duration = 0.5, distance = 24, direction = "up", once = true, className = "", }: StaggerListProps) { const ref = useRef(null); const isInView = useInView(ref, { once, amount: 0.15 }); const prefersReducedMotion = useReducedMotion(); /* * The stagger goes too, not just the travel. A long list under reduced * motion would otherwise still reveal itself one row at a time over several * seconds, which is the same distraction the setting asks to be spared. */ const container: Variants = { hidden: {}, visible: prefersReducedMotion ? {} : { transition: { staggerChildren: stagger, delayChildren: delay } }, }; const item: Variants = prefersReducedMotion ? { hidden: { opacity: 1, x: 0, y: 0 }, visible: { opacity: 1, x: 0, y: 0 }, } : { hidden: { opacity: 0, ...offsetFor(direction, distance) }, visible: { opacity: 1, x: 0, y: 0, transition: { duration, ease: [0.21, 0.47, 0.32, 0.98] }, }, }; return ( {/* Array.map over children rather than cloneElement, so children keep their own props and refs untouched. */} {Array.isArray(children) ? ( children.map((child, index) => ( {child} )) ) : ( {children} )} ); } export default StaggerList; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Animated Beam The AnimatedBeam component draws a curved line between any two elements and sends a gradient pulse along it, remeasuring whenever the layout moves. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/animated-beam - **Registry:** https://uibeats.com/r/animated-beam.json - **License:** MIT - **npm dependencies:** `lucide-react`, `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/animated-beam.json ``` Or install the dependencies and copy the source below: ```bash npm install lucide-react motion ``` ## When to use For architecture and integration diagrams, where the point is that data moves between two things. The path is measured live, so it survives reflow. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `containerRef` | `RefObject` | `-` | The positioned element both endpoints live inside (required) | | `fromRef` | `RefObject` | `-` | Element the beam starts at (required) | | `toRef` | `RefObject` | `-` | Element the beam ends at (required) | | `curvature` | `number` | `0` | Arc height in pixels; positive bows up, negative bows down | | `reverse` | `boolean` | `false` | Send the pulse from the end to the start | | `duration` | `number` | `3` | Seconds for one pulse to travel the path | | `delay` | `number` | `0` | Seconds before the pulse starts | | `pathColor` | `string` | `'var(--border)'` | Colour of the static path beneath the pulse | | `gradientStart` | `string` | `'var(--brand)'` | First colour of the travelling gradient | | `gradientStop` | `string` | `'var(--accent-pink)'` | Second colour of the travelling gradient | | `className` | `string` | `''` | Additional CSS classes for the SVG | ## Example usage ```tsx "use client"; import { useRef } from "react"; import { AnimatedBeam } from "@/components/demo/background/animated-beam"; import { Database, Sparkles } from "lucide-react"; import { GitHubIcon } from "@/components/icons"; const AnimatedBeamUsage = () => { const container = useRef(null); const left = useRef(null); const centre = useRef(null); const right = useRef(null); return (
); }; function Node({ ref, children, accent = false, }: { ref: React.Ref; children: React.ReactNode; accent?: boolean; }) { return (
{children}
); } export default AnimatedBeamUsage; ``` ## Source Save as `components/ui/animated-beam.tsx`. ```tsx "use client"; import { motion, useReducedMotion } from "motion/react"; import { useCallback, useEffect, useId, useState, type RefObject } from "react"; interface AnimatedBeamProps { /** The positioned element both refs live inside. */ containerRef: RefObject; fromRef: RefObject; toRef: RefObject; /** Arc height in pixels. Positive bows up, negative bows down, 0 is straight. */ curvature?: number; /** Send the pulse from `toRef` to `fromRef` instead. */ reverse?: boolean; duration?: number; delay?: number; pathColor?: string; gradientStart?: string; gradientStop?: string; className?: string; } /** * Draws a curved beam between two elements and sends a gradient pulse along * it. * * The path is measured from the live geometry of the two elements rather than * hardcoded, so it survives responsive reflow, font loading and content * changes: a `ResizeObserver` on the container recomputes it whenever * anything moves. */ export function AnimatedBeam({ containerRef, fromRef, toRef, curvature = 0, reverse = false, duration = 3, delay = 0, pathColor = "var(--border)", gradientStart = "var(--brand)", gradientStop = "var(--accent-pink)", className = "", }: AnimatedBeamProps) { // `useId` keeps the gradient id unique; two beams on one page sharing an id // would both resolve to whichever was defined last. const gradientId = useId(); const [path, setPath] = useState(""); const [size, setSize] = useState({ width: 0, height: 0 }); const prefersReducedMotion = useReducedMotion(); const measure = useCallback(() => { const container = containerRef.current; const from = fromRef.current; const to = toRef.current; if (!container || !from || !to) return; const containerRect = container.getBoundingClientRect(); const fromRect = from.getBoundingClientRect(); const toRect = to.getBoundingClientRect(); setSize({ width: containerRect.width, height: containerRect.height }); // Centres, expressed relative to the container's own coordinate space. const startX = fromRect.left - containerRect.left + fromRect.width / 2; const startY = fromRect.top - containerRect.top + fromRect.height / 2; const endX = toRect.left - containerRect.left + toRect.width / 2; const endY = toRect.top - containerRect.top + toRect.height / 2; const controlX = (startX + endX) / 2; const controlY = (startY + endY) / 2 - curvature; setPath(`M ${startX},${startY} Q ${controlX},${controlY} ${endX},${endY}`); }, [containerRef, fromRef, toRef, curvature]); useEffect(() => { measure(); const container = containerRef.current; if (!container) return; const observer = new ResizeObserver(measure); observer.observe(container); // Watch the endpoints too: the container can hold its size while the // elements inside it move. if (fromRef.current) observer.observe(fromRef.current); if (toRef.current) observer.observe(toRef.current); return () => observer.disconnect(); }, [measure, containerRef, fromRef, toRef]); if (!path) return null; return ( ); } export default AnimatedBeam; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Aurora The Aurora component drifts three soft bands of colour behind your content on different periods, so the layer keeps rearranging itself instead of looping visibly. Pure CSS gradients and transforms, no canvas. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/aurora - **Registry:** https://uibeats.com/r/aurora.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/aurora.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Behind a hero, a pricing header or an empty state that needs atmosphere rather than an image. Keep intensity low under body copy: the blur is wide enough that a bright aurora will pull contrast out from under the text sitting on top of it. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `from` | `string` | `'var(--brand)'` | Colour the ribbons start from. Any CSS colour | | `via` | `string` | `'var(--accent-pink)'` | Colour they pass through at their brightest | | `speed` | `number` | `14` | Seconds for one drift cycle (higher is slower) | | `blur` | `number` | `72` | How far the ribbons are softened, in pixels | | `intensity` | `number` | `0.55` | Overall opacity of the layer, 0 to 1 | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx "use client"; import { Aurora } from "@/components/demo/background/aurora"; const AuroraUsage = () => { return (

Northern lights

Three ribbons on different periods, so the field never settles into a pattern.

); }; export default AuroraUsage; ``` ## Source Save as `components/ui/aurora.tsx`. ```tsx "use client"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; interface AuroraProps { /** Colour the ribbons start from. Any CSS colour. */ from?: string; /** Colour they pass through at their brightest. */ via?: string; /** Seconds for one drift cycle. Higher is slower. */ speed?: number; /** How far the ribbons are softened, in pixels. */ blur?: number; /** Overall opacity of the layer, 0 to 1. */ intensity?: number; className?: string; } /** * Slow bands of colour drifting behind content, like an aurora. * * Three ribbons rather than one: a single animated gradient reads as a * gradient being animated, because every part of it moves in step. Overlapping * bands at different angles and, crucially, different periods never repeat * the same arrangement, so the layer keeps changing without any one element * doing anything complicated. * * The periods are derived from `speed` by fixed ratios instead of being offset * with delays: a delay only shifts where a loop starts, so the ribbons would * still realign every cycle and the whole field would pulse. * * Both colours default to theme tokens, so an installed aurora picks up the * project's accent rather than ours. */ /** Position, angle and period ratio per ribbon. */ const RIBBONS = [ { inset: "-30% -20% auto auto", size: "95% 85%", angle: -18, ratio: 1 }, { inset: "-15% auto auto -25%", size: "85% 105%", angle: 14, ratio: 1.37 }, { inset: "auto -15% -25% auto", size: "110% 75%", angle: -6, ratio: 0.79 }, ]; export function Aurora({ from = "var(--brand)", via = "var(--accent-pink)", speed = 14, blur = 72, intensity = 0.55, className = "", }: AuroraProps) { const prefersReducedMotion = useReducedMotion(); return ( ); } export default Aurora; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Dot Pattern The DotPattern component tiles a grid of dots behind your content and lights the ones nearest the pointer, using a masked second copy of the same grid so the bright dots land exactly on the dim ones. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/dot-pattern - **Registry:** https://uibeats.com/r/dot-pattern.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/dot-pattern.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use As the quiet texture under a hero, a feature section or an empty state, where a flat background would read as unfinished. Turn the glow off for anything behind long-form copy: a light that follows the cursor is an invitation to move it, which is the opposite of what a reading surface wants. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `spacing` | `number` | `20` | Distance between dot centres, in pixels | | `dotSize` | `number` | `1.4` | Diameter of each dot, in pixels | | `glow` | `boolean` | `true` | Light the dots nearest the pointer | | `glowRadius` | `number` | `160` | Radius of the lit area around the pointer, in pixels | | `className` | `string` | `''` | Additional CSS classes. Set the dot colour here, e.g. text-foreground | ## Example usage ```tsx "use client"; import { DotPattern } from "@/components/demo/background/dot-pattern"; const DotPatternUsage = () => { return (

Move the cursor

The same grid twice, with the bright copy masked to a circle around the pointer.

); }; export default DotPatternUsage; ``` ## Source Save as `components/ui/dot-pattern.tsx`. ```tsx "use client"; import { motion, useMotionTemplate, useMotionValue, useReducedMotion, } from "motion/react"; import { useId, type PointerEvent } from "react"; import { cn } from "@/lib/utils"; interface DotPatternProps { /** Distance between dot centres, in pixels. */ spacing?: number; /** Diameter of each dot, in pixels. */ dotSize?: number; /** Light the dots nearest the pointer. */ glow?: boolean; /** Radius of the lit area around the pointer, in pixels. */ glowRadius?: number; className?: string; } /** * A grid of dots that brightens around the pointer. * * Two copies of the same SVG pattern, one dim and one at full strength, with * the bright copy revealed through a radial mask that follows the cursor. That * is why the lit dots line up exactly with the dim ones: it is the same grid * twice, not a second grid drawn near the pointer. * * The mask is driven by motion values rather than state, so pointer movement * never re-renders the component; a `setState` per `pointermove` over a * full-bleed background is the version of this that drops frames on a laptop. * * Dots are painted with `currentColor`, so the grid takes the text colour of * whatever wraps it and needs nothing from the theme. */ export function DotPattern({ spacing = 20, dotSize = 1.4, glow = true, glowRadius = 160, className = "", }: DotPatternProps) { const prefersReducedMotion = useReducedMotion(); // Two patterns on a page would otherwise share an id and one would win. const patternId = useId(); const pointerX = useMotionValue(-glowRadius * 2); const pointerY = useMotionValue(-glowRadius * 2); const mask = useMotionTemplate`radial-gradient(${glowRadius}px circle at ${pointerX}px ${pointerY}px, #000 0%, transparent 100%)`; // Following the cursor is motion, so the preference turns the bright layer // off entirely and leaves the grid it sits on. const tracking = glow && !prefersReducedMotion; const handlePointerMove = (event: PointerEvent) => { const bounds = event.currentTarget.getBoundingClientRect(); pointerX.set(event.clientX - bounds.left); pointerY.set(event.clientY - bounds.top); }; const handlePointerLeave = () => { pointerX.set(-glowRadius * 2); pointerY.set(-glowRadius * 2); }; const grid = ( ); return ( ); } export default DotPattern; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Gradient Flow The GradientFlow component creates a flowing gradient animation effect for its children, adding visual interest to backgrounds or UI elements. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/gradient-flow - **Registry:** https://uibeats.com/r/gradient-flow.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/gradient-flow.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use A calm, always-moving backdrop for heroes and call-to-action sections. Because it never settles, keep it behind large type and avoid placing dense body copy on top of it. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | The content to be wrapped with the gradient flow effect (required) | | `duration` | `number` | `10` | Duration of the animation cycle (in seconds) | | `colors` | `string[]` | `['#ff0080', '#7928ca', '#ff4d4d']` | Array of colors to use in the gradient | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx import React from "react"; import GradientFlow from "@/components/demo/background/gradient-flow"; const GradientFlowUsage = () => { return (
Flowing Gradient Background
); }; export default GradientFlowUsage; ``` ## Source Save as `components/ui/gradient-flow.tsx`. ```tsx "use client"; import React from "react"; import { motion, useReducedMotion } from "motion/react"; interface GradientFlowProps { children: React.ReactNode; duration?: number; colors?: string[]; className?: string; fullWidth?: boolean; radialOverlay?: boolean; blurAmount?: string; } const GradientFlow: React.FC = ({ children, duration = 20, colors = ["#6366f1", "#2563eb", "#7c3aed", "#db2777"], className = "", fullWidth = false, radialOverlay = true, blurAmount = "10px", }) => { const prefersReducedMotion = useReducedMotion(); const gradientString = `linear-gradient(135deg, ${colors.join(", ")})`; const radialGradient = "radial-gradient(circle at 50% 50%, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%)"; return (
{radialOverlay && (
)}
{children}
); }; export default GradientFlow; ``` ## Credits Written by [Michael](https://github.com/lappemic). --- # Meteors The Meteors component drops a field of glowing streaks across its container, each one falling at its own pace so the shower never settles into a repeating pattern. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/meteors - **Registry:** https://uibeats.com/r/meteors.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/meteors.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Behind a hero or an empty state that reads flat on its own. Best on a dark surface, and keep the count low behind text. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `count` | `number` | `20` | How many streaks are in the air at once | | `angle` | `number` | `20` | Direction of travel, in degrees clockwise from straight down | | `duration` | `number` | `3` | Seconds for one streak to cross the frame, before per-streak variation | | `trailLength` | `number` | `90` | Length of a streak's tail in pixels, before variation | | `color` | `string` | `#ffffff` | Colour of the head and the tail it fades out of | | `className` | `string` | `''` | Additional CSS classes for styling | ## Example usage ```tsx "use client"; import { Meteors } from "@/components/demo/background/meteors"; const MeteorsUsage = () => { return (

Night shift

A quiet shower of light falling behind whatever you put in front of it.

); }; export default MeteorsUsage; ``` ## Source Save as `components/ui/meteors.tsx`. ```tsx "use client"; import { useMemo } from "react"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; interface MeteorsProps { /** How many streaks are in the air at once. */ count?: number; /** Direction of travel, in degrees clockwise from straight down. */ angle?: number; /** Seconds for one streak to cross the frame. */ duration?: number; /** Length of a streak's tail, in pixels, before per-streak variation. */ trailLength?: number; /** Colour of the head and the tail it fades out of. */ color?: string; className?: string; } /** * A deterministic 0–1 from an integer seed. * * `Math.random()` cannot be used for the layout: the meteors are placed during * render, so the server and the browser would each scatter them differently and * React would discard the server markup on hydration. Hashing the index gives * the same scattered-looking field on both, at no runtime cost. */ function pseudoRandom(seed: number): number { const value = Math.sin(seed * 127.1 + 311.7) * 43758.5453; return value - Math.floor(value); } /** * A field of meteors falling across whatever sits behind them. * * Each streak rides a rail the full height of the frame and travels in * percentages of that rail, so the field fills its container without measuring * it: no `ResizeObserver`, and nothing to retune when the container changes * size. Direction is one `rotate` on the field as a whole rather than a * transform per streak, which is also why the field is oversized by a quarter * in every direction: rotation would otherwise swing empty corners into view. */ export function Meteors({ count = 20, angle = 20, duration = 3, trailLength = 90, color = "#ffffff", className = "", }: MeteorsProps) { const prefersReducedMotion = useReducedMotion(); const meteors = useMemo( () => Array.from({ length: Math.max(0, Math.floor(count)) }, (_, index) => { // Spread either side of `duration` so the field keeps drifting out of // step instead of settling into one synchronised pulse. const travel = duration * (0.75 + pseudoRandom(index + 211) * 0.5); return { left: pseudoRandom(index) * 100, /* * Negative, so each streak begins part-way through its own fall * rather than at the top. A positive delay would leave the sky empty * for the first few seconds and fill it in, which is the one moment * a visitor is most likely to be looking at it. */ delay: -pseudoRandom(index + 101) * travel, travel, scale: 0.6 + pseudoRandom(index + 307) * 0.8, }; }), [count, duration], ); return ( ); } export default Meteors; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Orbiting Elements The OrbitingElements component places children evenly around a circle and rotates them around a centre point, keeping each one upright. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/orbiting-elements - **Registry:** https://uibeats.com/r/orbiting-elements.json - **License:** MIT - **npm dependencies:** `lucide-react`, `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/orbiting-elements.json ``` Or install the dependencies and copy the source below: ```bash npm install lucide-react motion ``` ## When to use For showing that several things revolve around one: integrations around a product, tools around a workflow. It reads best with a fixed centrepiece inside the orbit. Two rings at different radii and speeds give you depth, and three or four items per ring keeps them from colliding. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `children` | `React.ReactNode` | `-` | Elements to place around the orbit (required) | | `radius` | `number` | `100` | Orbit radius in pixels | | `duration` | `number` | `20` | Seconds for one full revolution | | `reverse` | `boolean` | `false` | Orbit anticlockwise | | `showPath` | `boolean` | `true` | Draw the dashed orbit path | | `startAngle` | `number` | `0` | Degrees to offset the first item by | | `className` | `string` | `''` | Additional CSS classes for the container | ## Example usage ```tsx "use client"; import { OrbitingElements } from "@/components/demo/background/orbiting-elements"; import { Atom, Boxes, Cpu, Sparkles } from "lucide-react"; const OrbitingElementsUsage = () => { return (
{[Atom, Cpu, Boxes].map((Icon, index) => (
))}
); }; export default OrbitingElementsUsage; ``` ## Source Save as `components/ui/orbiting-elements.tsx`. ```tsx "use client"; import { motion, useReducedMotion } from "motion/react"; import { Children, type ReactNode } from "react"; interface OrbitingElementsProps { children: ReactNode; /** Orbit radius in pixels. */ radius?: number; /** Seconds for one full revolution. */ duration?: number; reverse?: boolean; /** Draw the orbit path itself. */ showPath?: boolean; /** Degrees to offset the first item by. */ startAngle?: number; className?: string; } /** * Places children evenly around a circle and rotates them around a centre * point. * * Each item sits on a rotating arm and counter-rotates at the same speed, so * it travels the orbit while staying upright; icons and text never end up * upside down at the bottom of the circle. */ export function OrbitingElements({ children, radius = 100, duration = 20, reverse = false, showPath = true, startAngle = 0, className = "", }: OrbitingElementsProps) { const items = Children.toArray(children); const prefersReducedMotion = useReducedMotion(); const spin = reverse ? -360 : 360; const transition = { duration, repeat: Infinity, ease: "linear" as const, }; return (
{showPath ? ( ); } export default OrbitingElements; ``` ## Credits Written by [Claude Code](https://claude.com/claude-code). --- # Particles The Particles component draws a drifting field of specks on a canvas and scatters them away from the pointer, holding positions as fractions of the frame so a resize redraws the field instead of reshuffling it. - **Category:** Background - **Docs:** https://uibeats.com/docs/background/particles - **Registry:** https://uibeats.com/r/particles.json - **License:** MIT - **npm dependencies:** `motion` ## Installation ```bash npx shadcn@latest add https://uibeats.com/r/particles.json ``` Or install the dependencies and copy the source below: ```bash npm install motion ``` ## When to use Behind a hero or a launch page that wants depth and a reason to move the mouse. It paints on every frame, so keep the quantity modest and use one per view: a page with three particle fields spends more of its frame budget on decoration than on the interface. ## Props | Prop | Type | Default | Description | | --- | --- | --- | --- | | `quantity` | `number` | `60` | How many particles to draw | | `color` | `string` | `'currentColor'` | Colour of the particles. Any CSS colour, or currentColor | | `speed` | `number` | `0.03` | How far they drift, as a fraction of the frame per second | | `size` | `number` | `2` | Largest particle radius, in pixels | | `repel` | `number` | `90` | Radius around the pointer that pushes particles away, in pixels | | `className` | `string` | `''` | Additional CSS classes. Set the colour here when using currentColor | ## Example usage ```tsx "use client"; import { Particles } from "@/components/demo/background/particles"; const ParticlesUsage = () => { return (

Drift

Sixty specks on a canvas, scattering out of the way wherever the pointer goes.

); }; export default ParticlesUsage; ``` ## Source Save as `components/ui/particles.tsx`. ```tsx "use client"; import { useReducedMotion } from "motion/react"; import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; interface ParticlesProps { /** How many particles to draw. */ quantity?: number; /** Colour of the particles. Any CSS colour, or `currentColor`. */ color?: string; /** How far they drift, as a fraction of the frame per second. */ speed?: number; /** Largest particle radius, in pixels. */ size?: number; /** Radius around the pointer that pushes particles away, in pixels. */ repel?: number; className?: string; } /** One particle, positioned in fractions of the frame rather than pixels. */ interface Particle { /** Position, 0 to 1 on each axis. */ nx: number; ny: number; /** Velocity, in fractions of the frame per second. */ vx: number; vy: number; radius: number; alpha: number; /** Pixel offset from the cursor push, eased back to zero. */ ox: number; oy: number; } /** * A deterministic pseudo-random source (mulberry32). * * `Math.random()` would give every visitor a different field, which is the * usual and correct choice, but it also gives every CI run a different one, * and `tests/visual/frames.spec.ts` screenshots this component at three fixed * points. A fixed seed makes those frames reproducible without freezing the * motion itself. */ function createRandom(seed: number): () => number { let state = seed; return () => { state = (state + 0x6d2b79f5) | 0; let t = Math.imul(state ^ (state >>> 15), 1 | state); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } /** * A drifting field of particles that scatters away from the pointer. * * Positions are held as fractions of the frame rather than pixels, so a resize * is a redraw rather than a reseed: the field keeps its arrangement when the * container changes instead of visibly reshuffling. * * Live props are read through a ref inside the animation loop, so dragging a * slider changes the motion without tearing down and reseeding the field. Only * `quantity` and `size` rebuild it, because those are what it is made of. * * With the preference set it draws one frame and stops: a static starfield is * still a background, where a moving one is the thing the reader asked not to * see. */ export function Particles({ quantity = 60, color = "currentColor", speed = 0.03, size = 2, repel = 90, className = "", }: ParticlesProps) { const prefersReducedMotion = useReducedMotion(); const canvasRef = useRef(null); const particlesRef = useRef([]); // Read inside the loop so a prop change does not restart it. const liveRef = useRef({ color, speed, repel }); useEffect(() => { liveRef.current = { color, speed, repel }; }, [color, speed, repel]); useEffect(() => { const random = createRandom(0x5eed); particlesRef.current = Array.from({ length: quantity }, () => ({ nx: random(), ny: random(), // Centred on zero so the field has no net drift in any direction. vx: random() - 0.5, vy: random() - 0.5, radius: 0.35 * size + random() * 0.65 * size, alpha: 0.25 + random() * 0.55, ox: 0, oy: 0, })); }, [quantity, size]); useEffect(() => { const canvas = canvasRef.current; const context = canvas?.getContext("2d"); if (!canvas || !context) return; const pointer = { x: -Infinity, y: -Infinity }; let width = 0; let height = 0; const resize = () => { const ratio = window.devicePixelRatio || 1; const bounds = canvas.getBoundingClientRect(); width = bounds.width; height = bounds.height; // Back the canvas at device resolution, then work in CSS pixels. canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio); context.setTransform(ratio, 0, 0, ratio, 0, 0); }; const draw = (delta: number) => { const live = liveRef.current; context.clearRect(0, 0, width, height); // `currentColor` is not a value a canvas understands, so it is resolved // from the element the same way CSS would. const paint = live.color === "currentColor" ? getComputedStyle(canvas).color : live.color; for (const particle of particlesRef.current) { if (delta > 0) { particle.nx += particle.vx * live.speed * delta; particle.ny += particle.vy * live.speed * delta; // Wrap rather than bounce: a bounce makes the edges of the frame // visible, which is the one thing a background should not do. if (particle.nx < 0) particle.nx += 1; if (particle.nx > 1) particle.nx -= 1; if (particle.ny < 0) particle.ny += 1; if (particle.ny > 1) particle.ny -= 1; } const x = particle.nx * width; const y = particle.ny * height; if (live.repel > 0 && delta > 0) { const toX = x - pointer.x; const toY = y - pointer.y; const distance = Math.hypot(toX, toY); if (distance < live.repel && distance > 0.01) { const push = (1 - distance / live.repel) * live.repel * 0.6; particle.ox += ((toX / distance) * push - particle.ox) * 0.2; particle.oy += ((toY / distance) * push - particle.oy) * 0.2; } else { // Ease home rather than snapping, so leaving the field settles. particle.ox *= 0.92; particle.oy *= 0.92; } } context.globalAlpha = particle.alpha; context.fillStyle = paint; context.beginPath(); context.arc( x + particle.ox, y + particle.oy, particle.radius, 0, Math.PI * 2, ); context.fill(); } context.globalAlpha = 1; }; resize(); const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => { resize(); draw(0); }); observer?.observe(canvas); if (prefersReducedMotion) { draw(0); return () => observer?.disconnect(); } const handlePointerMove = (event: PointerEvent) => { const bounds = canvas.getBoundingClientRect(); pointer.x = event.clientX - bounds.left; pointer.y = event.clientY - bounds.top; }; const handlePointerLeave = () => { pointer.x = -Infinity; pointer.y = -Infinity; }; canvas.addEventListener("pointermove", handlePointerMove); canvas.addEventListener("pointerleave", handlePointerLeave); let frame = 0; let previous = 0; const tick = (time: number) => { // Clamped: a backgrounded tab resumes with a delta of several seconds, // which would teleport every particle across the frame at once. const delta = previous ? Math.min((time - previous) / 1000, 0.05) : 0; previous = time; draw(delta); frame = requestAnimationFrame(tick); }; frame = requestAnimationFrame(tick); return () => { cancelAnimationFrame(frame); observer?.disconnect(); canvas.removeEventListener("pointermove", handlePointerMove); canvas.removeEventListener("pointerleave", handlePointerLeave); }; }, [prefersReducedMotion]); return (