We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide45 min read

Aceternity UI - Komponenty React z Efektami 3D i Animacjami

Aceternity UI is a collection of free, copy-paste React components with advanced 3D effects, parallax, glow effects and Framer Motion animations. Perfect for creating modern landing pages.

Aceternity UI - Komponenty React z Efektami 3D i Animacjami

Czym jest Aceternity UI?

Aceternity UI to kolekcja darmowych, open-source komponentów React zaprojektowanych z myślą o tworzeniu spektakularnych efektów wizualnych. Biblioteka oferuje gotowe do użycia komponenty z zaawansowanymi animacjami 3D, efektami parallax, świeceniami (glow), spotlight effects i wieloma innymi efektami wizualnymi, które nadają aplikacjom i stronom internetowym profesjonalny, nowoczesny wygląd.

Aceternity UI powstało jako odpowiedź na rosnące zapotrzebowanie deweloperów na łatwo dostępne, wysokiej jakości komponenty wizualne. Twórcy biblioteki zebrali najczęściej spotykane efekty z nowoczesnych stron internetowych firm technologicznych i udostępnili je w formie copy-paste komponentów, które można łatwo dostosować do własnych potrzeb.

Biblioteka jest zbudowana na fundamentach Tailwind CSS i Framer Motion, co zapewnia doskonałą wydajność animacji i łatwość customizacji. Komponenty są w pełni responsywne i wspierają tryb ciemny (dark mode) out of the box.

Dlaczego Aceternity UI?

Kluczowe zalety

  1. Copy-Paste Approach - Każdy komponent to gotowy kod do skopiowania
  2. Framer Motion - Płynne, wydajne animacje 60fps
  3. Tailwind CSS - Pełna kontrola nad stylami
  4. TypeScript - Pełne wsparcie typów
  5. Zero Dependencies - Tylko Tailwind i Framer Motion
  6. Dark Mode - Natywne wsparcie trybu ciemnego
  7. Responsywność - Mobile-first design
  8. Dokumentacja - Szczegółowe przykłady użycia

Aceternity UI vs Magic UI vs shadcn/ui

CechaAceternity UIMagic UIshadcn/ui
FocusEfekty 3D/wizualneAnimacje microForm components
AnimacjeBardzo zaawansowaneZaawansowanePodstawowe
Efekty 3DTak, główny focusCzęściowoNie
Copy-pasteTakTakTak
TailwindTakTakTak
Framer MotionWymaganeWymaganeOpcjonalne
Ilość komponentów50+100+50+
Best forLanding pagesMarketingAplikacje

Instalacja i konfiguracja

Wymagane zależności

Code
Bash
# Podstawowe zależności
npm install framer-motion clsx tailwind-merge

# Opcjonalne dla niektórych komponentów
npm install @tabler/icons-react
npm install simplex-noise  # dla efektów particle

Konfiguracja Tailwind CSS

JStailwind.config.js
JavaScript
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      animation: {
        spotlight: 'spotlight 2s ease .75s 1 forwards',
        shimmer: 'shimmer 2s linear infinite',
        'meteor-effect': 'meteor 5s linear infinite',
        aurora: 'aurora 60s linear infinite',
        'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
      },
      keyframes: {
        spotlight: {
          '0%': {
            opacity: 0,
            transform: 'translate(-72%, -62%) scale(0.5)',
          },
          '100%': {
            opacity: 1,
            transform: 'translate(-50%,-40%) scale(1)',
          },
        },
        shimmer: {
          from: {
            backgroundPosition: '0 0',
          },
          to: {
            backgroundPosition: '-200% 0',
          },
        },
        meteor: {
          '0%': { transform: 'rotate(215deg) translateX(0)', opacity: 1 },
          '70%': { opacity: 1 },
          '100%': {
            transform: 'rotate(215deg) translateX(-500px)',
            opacity: 0,
          },
        },
        aurora: {
          from: {
            backgroundPosition: '50% 50%, 50% 50%',
          },
          to: {
            backgroundPosition: '350% 50%, 350% 50%',
          },
        },
        'border-beam': {
          '100%': {
            'offset-distance': '100%',
          },
        },
      },
    },
  },
  plugins: [],
}

Utility funkcja cn()

TSlib/utils.ts
TypeScript
// lib/utils.ts
import { ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Popularne komponenty Aceternity UI

3D Card Effect

Karta z efektem 3D follow-mouse - najbardziej ikoniczny komponent biblioteki.

TScomponents/ui/3d-card.tsx
TypeScript
// components/ui/3d-card.tsx
'use client'

import { cn } from '@/lib/utils'
import React, {
  createContext,
  useState,
  useContext,
  useRef,
  useEffect,
} from 'react'

const MouseEnterContext = createContext<
  [boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined
>(undefined)

export const CardContainer = ({
  children,
  className,
  containerClassName,
}: {
  children?: React.ReactNode
  className?: string
  containerClassName?: string
}) => {
  const containerRef = useRef<HTMLDivElement>(null)
  const [isMouseEntered, setIsMouseEntered] = useState(false)

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!containerRef.current) return
    const { left, top, width, height } =
      containerRef.current.getBoundingClientRect()
    const x = (e.clientX - left - width / 2) / 25
    const y = (e.clientY - top - height / 2) / 25
    containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`
  }

  const handleMouseEnter = () => {
    setIsMouseEntered(true)
  }

  const handleMouseLeave = () => {
    if (!containerRef.current) return
    setIsMouseEntered(false)
    containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`
  }

  return (
    <MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
      <div
        className={cn(
          'py-20 flex items-center justify-center',
          containerClassName
        )}
        style={{
          perspective: '1000px',
        }}
      >
        <div
          ref={containerRef}
          onMouseEnter={handleMouseEnter}
          onMouseMove={handleMouseMove}
          onMouseLeave={handleMouseLeave}
          className={cn(
            'flex items-center justify-center relative transition-all duration-200 ease-linear',
            className
          )}
          style={{
            transformStyle: 'preserve-3d',
          }}
        >
          {children}
        </div>
      </div>
    </MouseEnterContext.Provider>
  )
}

export const CardBody = ({
  children,
  className,
}: {
  children: React.ReactNode
  className?: string
}) => {
  return (
    <div
      className={cn(
        'h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d]',
        className
      )}
    >
      {children}
    </div>
  )
}

export const CardItem = ({
  as: Tag = 'div',
  children,
  className,
  translateX = 0,
  translateY = 0,
  translateZ = 0,
  rotateX = 0,
  rotateY = 0,
  rotateZ = 0,
  ...rest
}: {
  as?: React.ElementType
  children: React.ReactNode
  className?: string
  translateX?: number | string
  translateY?: number | string
  translateZ?: number | string
  rotateX?: number | string
  rotateY?: number | string
  rotateZ?: number | string
  [key: string]: unknown
}) => {
  const ref = useRef<HTMLDivElement>(null)
  const [isMouseEntered] = useMouseEnter()

  useEffect(() => {
    handleAnimations()
  }, [isMouseEntered])

  const handleAnimations = () => {
    if (!ref.current) return
    if (isMouseEntered) {
      ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`
    } else {
      ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`
    }
  }

  return (
    <Tag
      ref={ref}
      className={cn('w-fit transition duration-200 ease-linear', className)}
      {...rest}
    >
      {children}
    </Tag>
  )
}

export const useMouseEnter = () => {
  const context = useContext(MouseEnterContext)
  if (context === undefined) {
    throw new Error('useMouseEnter must be used within a MouseEnterProvider')
  }
  return context
}

Przykład użycia 3D Card:

Code
TypeScript
import { CardContainer, CardBody, CardItem } from '@/components/ui/3d-card'
import Image from 'next/image'

export function ThreeDCardDemo() {
  return (
    <CardContainer className="inter-var">
      <CardBody className="bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-auto sm:w-[30rem] h-auto rounded-xl p-6 border">
        <CardItem
          translateZ="50"
          className="text-xl font-bold text-neutral-600 dark:text-white"
        >
          Make things float in air
        </CardItem>
        <CardItem
          as="p"
          translateZ="60"
          className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
        >
          Hover over this card to unleash the power of CSS perspective
        </CardItem>
        <CardItem translateZ="100" className="w-full mt-4">
          <Image
            src="/images/product.png"
            height="1000"
            width="1000"
            className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
            alt="thumbnail"
          />
        </CardItem>
        <div className="flex justify-between items-center mt-20">
          <CardItem
            translateZ={20}
            as="button"
            className="px-4 py-2 rounded-xl text-xs font-normal dark:text-white"
          >
            Try now →
          </CardItem>
          <CardItem
            translateZ={20}
            as="button"
            className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold"
          >
            Sign up
          </CardItem>
        </div>
      </CardBody>
    </CardContainer>
  )
}

Spotlight Effect

Efekt spotlight podążający za kursorem - świetny do hero sections.

TScomponents/ui/spotlight.tsx
TypeScript
// components/ui/spotlight.tsx
'use client'

import React from 'react'
import { cn } from '@/lib/utils'

type SpotlightProps = {
  className?: string
  fill?: string
}

export const Spotlight = ({ className, fill }: SpotlightProps) => {
  return (
    <svg
      className={cn(
        'animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%] opacity-0',
        className
      )}
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 3787 2842"
      fill="none"
    >
      <g filter="url(#filter)">
        <ellipse
          cx="1924.71"
          cy="273.501"
          rx="1924.71"
          ry="273.501"
          transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
          fill={fill || 'white'}
          fillOpacity="0.21"
        ></ellipse>
      </g>
      <defs>
        <filter
          id="filter"
          x="0.860352"
          y="0.838989"
          width="3785.16"
          height="2840.26"
          filterUnits="userSpaceOnUse"
          colorInterpolationFilters="sRGB"
        >
          <feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood>
          <feBlend
            mode="normal"
            in="SourceGraphic"
            in2="BackgroundImageFix"
            result="shape"
          ></feBlend>
          <feGaussianBlur
            stdDeviation="151"
            result="effect1_foregroundBlur_1065_8"
          ></feGaussianBlur>
        </filter>
      </defs>
    </svg>
  )
}

Przykład użycia Spotlight:

Code
TypeScript
import { Spotlight } from '@/components/ui/spotlight'

export function SpotlightPreview() {
  return (
    <div className="h-[40rem] w-full rounded-md flex md:items-center md:justify-center bg-black/[0.96] antialiased bg-grid-white/[0.02] relative overflow-hidden">
      <Spotlight
        className="-top-40 left-0 md:left-60 md:-top-20"
        fill="white"
      />
      <div className="p-4 max-w-7xl mx-auto relative z-10 w-full pt-20 md:pt-0">
        <h1 className="text-4xl md:text-7xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-b from-neutral-50 to-neutral-400 bg-opacity-50">
          Spotlight <br /> is the new trend.
        </h1>
        <p className="mt-4 font-normal text-base text-neutral-300 max-w-lg text-center mx-auto">
          Spotlight effect is a great way to draw attention to a specific part
          of the page. Here, we are drawing the attention towards the text
          section of the page. I don&apos;t know what else to write so I&apos;ll
          just keep writing gibberish.
        </p>
      </div>
    </div>
  )
}

Meteors Effect

Spadające meteory w tle - efekt wizualny inspirowany kosmosem.

TScomponents/ui/meteors.tsx
TypeScript
// components/ui/meteors.tsx
'use client'

import { cn } from '@/lib/utils'
import React from 'react'

export const Meteors = ({
  number,
  className,
}: {
  number?: number
  className?: string
}) => {
  const meteors = new Array(number || 20).fill(true)
  return (
    <>
      {meteors.map((_, idx) => (
        <span
          key={'meteor' + idx}
          className={cn(
            'animate-meteor-effect absolute top-1/2 left-1/2 h-0.5 w-0.5 rounded-[9999px] bg-slate-500 shadow-[0_0_0_1px_#ffffff10] rotate-[215deg]',
            "before:content-[''] before:absolute before:top-1/2 before:-translate-y-[50%] before:w-[50px] before:h-[1px] before:bg-gradient-to-r before:from-[#64748b] before:to-transparent",
            className
          )}
          style={{
            top: 0,
            left: Math.floor(Math.random() * (400 - -400) + -400) + 'px',
            animationDelay: Math.random() * (0.8 - 0.2) + 0.2 + 's',
            animationDuration: Math.floor(Math.random() * (10 - 2) + 2) + 's',
          }}
        ></span>
      ))}
    </>
  )
}

Przykład użycia Meteors:

Code
TypeScript
import { Meteors } from '@/components/ui/meteors'

export function MeteorsDemo() {
  return (
    <div className="w-full relative max-w-xs">
      <div className="absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] bg-red-500 rounded-full blur-3xl" />
      <div className="relative shadow-xl bg-gray-900 border border-gray-800 px-4 py-8 h-full overflow-hidden rounded-2xl flex flex-col justify-end items-start">
        <div className="h-5 w-5 rounded-full border flex items-center justify-center mb-4 border-gray-500">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth="1.5"
            stroke="currentColor"
            className="h-2 w-2 text-gray-300"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M4.5 4.5l15 15m0 0V8.25m0 11.25H8.25"
            />
          </svg>
        </div>

        <h1 className="font-bold text-xl text-white mb-4 relative z-50">
          Meteors because they&apos;re cool
        </h1>

        <p className="font-normal text-base text-slate-500 mb-4 relative z-50">
          I don&apos;t know what to write so I&apos;ll just paste something cool
          here. One more sentence because lorem ipsum is boring.
        </p>

        <button className="border px-4 py-1 rounded-lg border-gray-500 text-gray-300">
          Explore
        </button>

        <Meteors number={20} />
      </div>
    </div>
  )
}

Aurora Background

Animowane aurora borealis w tle - efekt zorzy polarnej.

TScomponents/ui/aurora-background.tsx
TypeScript
// components/ui/aurora-background.tsx
'use client'

import { cn } from '@/lib/utils'
import React, { ReactNode } from 'react'

interface AuroraBackgroundProps extends React.HTMLProps<HTMLDivElement> {
  children: ReactNode
  showRadialGradient?: boolean
}

export const AuroraBackground = ({
  className,
  children,
  showRadialGradient = true,
  ...props
}: AuroraBackgroundProps) => {
  return (
    <main>
      <div
        className={cn(
          'relative flex flex-col h-[100vh] items-center justify-center bg-zinc-50 dark:bg-zinc-900 text-slate-950 transition-bg',
          className
        )}
        {...props}
      >
        <div className="absolute inset-0 overflow-hidden">
          <div
            className={cn(
              `
            [--white-gradient:repeating-linear-gradient(100deg,var(--white)_0%,var(--white)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--white)_16%)]
            [--dark-gradient:repeating-linear-gradient(100deg,var(--black)_0%,var(--black)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--black)_16%)]
            [--aurora:repeating-linear-gradient(100deg,var(--blue-500)_10%,var(--indigo-300)_15%,var(--blue-300)_20%,var(--violet-200)_25%,var(--blue-400)_30%)]
            [background-image:var(--white-gradient),var(--aurora)]
            dark:[background-image:var(--dark-gradient),var(--aurora)]
            [background-size:300%,_200%]
            [background-position:50%_50%,50%_50%]
            filter blur-[10px] invert dark:invert-0
            after:content-[""] after:absolute after:inset-0 after:[background-image:var(--white-gradient),var(--aurora)]
            after:dark:[background-image:var(--dark-gradient),var(--aurora)]
            after:[background-size:200%,_100%]
            after:animate-aurora after:[background-attachment:fixed] after:mix-blend-difference
            pointer-events-none
            absolute -inset-[10px] opacity-50 will-change-transform`,
              showRadialGradient &&
                `[mask-image:radial-gradient(ellipse_at_100%_0%,black_10%,var(--transparent)_70%)]`
            )}
          ></div>
        </div>
        {children}
      </div>
    </main>
  )
}

Przykład użycia Aurora Background:

Code
TypeScript
'use client'

import { motion } from 'framer-motion'
import React from 'react'
import { AuroraBackground } from '@/components/ui/aurora-background'

export function AuroraBackgroundDemo() {
  return (
    <AuroraBackground>
      <motion.div
        initial={{ opacity: 0.0, y: 40 }}
        whileInView={{ opacity: 1, y: 0 }}
        transition={{
          delay: 0.3,
          duration: 0.8,
          ease: 'easeInOut',
        }}
        className="relative flex flex-col gap-4 items-center justify-center px-4"
      >
        <div className="text-3xl md:text-7xl font-bold dark:text-white text-center">
          Background lights are cool you know.
        </div>
        <div className="font-extralight text-base md:text-4xl dark:text-neutral-200 py-4">
          And this, is chemical burn.
        </div>
        <button className="bg-black dark:bg-white rounded-full w-fit text-white dark:text-black px-4 py-2">
          Debug now
        </button>
      </motion.div>
    </AuroraBackground>
  )
}

Sparkles Effect

Animowane iskierki wokół elementu - subtelny efekt magii.

TScomponents/ui/sparkles.tsx
TypeScript
// components/ui/sparkles.tsx
'use client'

import React, { useId, useMemo } from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

interface SparkleProps {
  id: string
  createdAt: number
  color: string
  size: number
  style: {
    top: string
    left: string
    zIndex: number
  }
}

interface SparklesProps {
  className?: string
  children: React.ReactNode
  sparklesCount?: number
  colors?: {
    first: string
    second: string
  }
}

const DEFAULT_SPARKLE_COLORS = {
  first: '#9E7AFF',
  second: '#FE8BBB',
}

const generateSparkle = (colors = DEFAULT_SPARKLE_COLORS): SparkleProps => {
  return {
    id: String(Math.random()),
    createdAt: Date.now(),
    color: Math.random() > 0.5 ? colors.first : colors.second,
    size: Math.random() * 10 + 10,
    style: {
      top: Math.random() * 100 + '%',
      left: Math.random() * 100 + '%',
      zIndex: 2,
    },
  }
}

const Sparkle = ({ color, size, style }: Omit<SparkleProps, 'id' | 'createdAt'>) => {
  return (
    <motion.svg
      width={size}
      height={size}
      viewBox="0 0 160 160"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      style={style}
      className="absolute pointer-events-none"
      initial={{ scale: 0, rotate: 0 }}
      animate={{
        scale: [0, 1, 0],
        rotate: [0, 180],
      }}
      transition={{
        duration: 1.5,
        repeat: Infinity,
        repeatDelay: Math.random() * 2,
      }}
    >
      <path
        d="M80 0C80 0 84.2846 41.2925 101.496 58.504C118.707 75.7154 160 80 160 80C160 80 118.707 84.2846 101.496 101.496C84.2846 118.707 80 160 80 160C80 160 75.7154 118.707 58.504 101.496C41.2925 84.2846 0 80 0 80C0 80 41.2925 75.7154 58.504 58.504C75.7154 41.2925 80 0 80 0Z"
        fill={color}
      />
    </motion.svg>
  )
}

export const Sparkles: React.FC<SparklesProps> = ({
  children,
  className,
  sparklesCount = 10,
  colors = DEFAULT_SPARKLE_COLORS,
}) => {
  const sparkles = useMemo(() => {
    return Array.from({ length: sparklesCount }, () => generateSparkle(colors))
  }, [sparklesCount, colors])

  return (
    <span className={cn('relative inline-block', className)}>
      {sparkles.map((sparkle) => (
        <Sparkle
          key={sparkle.id}
          color={sparkle.color}
          size={sparkle.size}
          style={sparkle.style}
        />
      ))}
      <span className="relative z-10">{children}</span>
    </span>
  )
}

Przykład użycia Sparkles:

Code
TypeScript
import { Sparkles } from '@/components/ui/sparkles'

export function SparklesPreview() {
  return (
    <div className="h-40 w-full bg-black flex items-center justify-center">
      <Sparkles
        sparklesCount={12}
        colors={{
          first: '#A07CFE',
          second: '#FE8FB5',
        }}
      >
        <span className="text-4xl font-bold text-white">
          Magical Text
        </span>
      </Sparkles>
    </div>
  )
}

Lamp Effect

Świecąca lampa z animowanym światłem - dramatyczny efekt hero.

TScomponents/ui/lamp.tsx
TypeScript
// components/ui/lamp.tsx
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export const LampContainer = ({
  children,
  className,
}: {
  children: React.ReactNode
  className?: string
}) => {
  return (
    <div
      className={cn(
        'relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-slate-950 w-full rounded-md z-0',
        className
      )}
    >
      <div className="relative flex w-full flex-1 scale-y-125 items-center justify-center isolate z-0">
        <motion.div
          initial={{ opacity: 0.5, width: '15rem' }}
          whileInView={{ opacity: 1, width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          style={{
            backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
          }}
          className="absolute inset-auto right-1/2 h-56 overflow-visible w-[30rem] bg-gradient-conic from-cyan-500 via-transparent to-transparent text-white [--conic-position:from_70deg_at_center_top]"
        >
          <div className="absolute w-[100%] left-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
          <div className="absolute w-40 h-[100%] left-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_right,white,transparent)]" />
        </motion.div>
        <motion.div
          initial={{ opacity: 0.5, width: '15rem' }}
          whileInView={{ opacity: 1, width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          style={{
            backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
          }}
          className="absolute inset-auto left-1/2 h-56 w-[30rem] bg-gradient-conic from-transparent via-transparent to-cyan-500 text-white [--conic-position:from_290deg_at_center_top]"
        >
          <div className="absolute w-40 h-[100%] right-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_left,white,transparent)]" />
          <div className="absolute w-[100%] right-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
        </motion.div>
        <div className="absolute top-1/2 h-48 w-full translate-y-12 scale-x-150 bg-slate-950 blur-2xl"></div>
        <div className="absolute top-1/2 z-50 h-48 w-full bg-transparent opacity-10 backdrop-blur-md"></div>
        <div className="absolute inset-auto z-50 h-36 w-[28rem] -translate-y-1/2 rounded-full bg-cyan-500 opacity-50 blur-3xl"></div>
        <motion.div
          initial={{ width: '8rem' }}
          whileInView={{ width: '16rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          className="absolute inset-auto z-30 h-36 w-64 -translate-y-[6rem] rounded-full bg-cyan-400 blur-2xl"
        ></motion.div>
        <motion.div
          initial={{ width: '15rem' }}
          whileInView={{ width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          className="absolute inset-auto z-50 h-0.5 w-[30rem] -translate-y-[7rem] bg-cyan-400"
        ></motion.div>

        <div className="absolute inset-auto z-40 h-44 w-full -translate-y-[12.5rem] bg-slate-950"></div>
      </div>

      <div className="relative z-50 flex -translate-y-80 flex-col items-center px-5">
        {children}
      </div>
    </div>
  )
}

Przykład użycia Lamp Effect:

Code
TypeScript
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { LampContainer } from '@/components/ui/lamp'

export function LampDemo() {
  return (
    <LampContainer>
      <motion.h1
        initial={{ opacity: 0.5, y: 100 }}
        whileInView={{ opacity: 1, y: 0 }}
        transition={{
          delay: 0.3,
          duration: 0.8,
          ease: 'easeInOut',
        }}
        className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4 bg-clip-text text-center text-4xl font-medium tracking-tight text-transparent md:text-7xl"
      >
        Build lamps <br /> the right way
      </motion.h1>
    </LampContainer>
  )
}

Background Beams

Animowane wiązki świetlne w tle.

TScomponents/ui/background-beams.tsx
TypeScript
// components/ui/background-beams.tsx
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export const BackgroundBeams = ({ className }: { className?: string }) => {
  const paths = [
    'M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875',
    'M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867',
    'M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859',
    'M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851',
    'M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843',
    'M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835',
    'M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827',
    'M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819',
    'M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811',
    'M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803',
  ]

  return (
    <div
      className={cn(
        'absolute h-full w-full inset-0 [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center',
        className
      )}
    >
      <svg
        className="z-0 h-full w-full pointer-events-none absolute"
        width="100%"
        height="100%"
        viewBox="0 0 696 316"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        {paths.map((path, index) => (
          <motion.path
            key={`path-` + index}
            d={path}
            stroke={`url(#linearGradient-${index})`}
            strokeOpacity="0.4"
            strokeWidth="0.5"
          ></motion.path>
        ))}
        <defs>
          {paths.map((_, index) => (
            <motion.linearGradient
              id={`linearGradient-${index}`}
              key={`gradient-${index}`}
              initial={{
                x1: '0%',
                x2: '0%',
                y1: '0%',
                y2: '0%',
              }}
              animate={{
                x1: ['0%', '100%'],
                x2: ['0%', '95%'],
                y1: ['0%', '100%'],
                y2: ['0%', `${93 + Math.random() * 8}%`],
              }}
              transition={{
                duration: Math.random() * 10 + 10,
                ease: 'easeInOut',
                repeat: Infinity,
                delay: Math.random() * 10,
              }}
            >
              <stop stopColor="#18CCFC" stopOpacity="0"></stop>
              <stop stopColor="#18CCFC"></stop>
              <stop offset="32.5%" stopColor="#6344F5"></stop>
              <stop offset="100%" stopColor="#AE48FF" stopOpacity="0"></stop>
            </motion.linearGradient>
          ))}
        </defs>
      </svg>
    </div>
  )
}

Text Generate Effect

Animowane generowanie tekstu litera po literze.

TScomponents/ui/text-generate-effect.tsx
TypeScript
// components/ui/text-generate-effect.tsx
'use client'

import { useEffect } from 'react'
import { motion, stagger, useAnimate } from 'framer-motion'
import { cn } from '@/lib/utils'

export const TextGenerateEffect = ({
  words,
  className,
  filter = true,
  duration = 0.5,
}: {
  words: string
  className?: string
  filter?: boolean
  duration?: number
}) => {
  const [scope, animate] = useAnimate()
  const wordsArray = words.split(' ')

  useEffect(() => {
    animate(
      'span',
      {
        opacity: 1,
        filter: filter ? 'blur(0px)' : 'none',
      },
      {
        duration: duration ? duration : 1,
        delay: stagger(0.2),
      }
    )
  }, [scope.current])

  const renderWords = () => {
    return (
      <motion.div ref={scope}>
        {wordsArray.map((word, idx) => {
          return (
            <motion.span
              key={word + idx}
              className="dark:text-white text-black opacity-0"
              style={{
                filter: filter ? 'blur(10px)' : 'none',
              }}
            >
              {word}{' '}
            </motion.span>
          )
        })}
      </motion.div>
    )
  }

  return (
    <div className={cn('font-bold', className)}>
      <div className="mt-4">
        <div className="dark:text-white text-black text-2xl leading-snug tracking-wide">
          {renderWords()}
        </div>
      </div>
    </div>
  )
}

Przykład użycia Text Generate Effect:

Code
TypeScript
import { TextGenerateEffect } from '@/components/ui/text-generate-effect'

const words = `Oxygen gets you high. In a catastrophic emergency, we're taking giant, panicked breaths. Suddenly you become euphoric, docile. You accept your fate.`

export function TextGenerateEffectDemo() {
  return <TextGenerateEffect words={words} />
}

Zaawansowane komponenty

Animated Tabs

TScomponents/ui/animated-tabs.tsx
TypeScript
// components/ui/animated-tabs.tsx
'use client'

import { useState } from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

type Tab = {
  title: string
  value: string
  content?: string | React.ReactNode
}

export const Tabs = ({
  tabs: propTabs,
  containerClassName,
  activeTabClassName,
  tabClassName,
  contentClassName,
}: {
  tabs: Tab[]
  containerClassName?: string
  activeTabClassName?: string
  tabClassName?: string
  contentClassName?: string
}) => {
  const [active, setActive] = useState<Tab>(propTabs[0])
  const [tabs, setTabs] = useState<Tab[]>(propTabs)

  const moveSelectedTabToTop = (idx: number) => {
    const newTabs = [...propTabs]
    const selectedTab = newTabs.splice(idx, 1)
    newTabs.unshift(selectedTab[0])
    setTabs(newTabs)
    setActive(newTabs[0])
  }

  const [hovering, setHovering] = useState(false)

  return (
    <>
      <div
        className={cn(
          'flex flex-row items-center justify-start [perspective:1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar max-w-full w-full',
          containerClassName
        )}
      >
        {propTabs.map((tab, idx) => (
          <button
            key={tab.title}
            onClick={() => {
              moveSelectedTabToTop(idx)
            }}
            onMouseEnter={() => setHovering(true)}
            onMouseLeave={() => setHovering(false)}
            className={cn('relative px-4 py-2 rounded-full', tabClassName)}
            style={{
              transformStyle: 'preserve-3d',
            }}
          >
            {active.value === tab.value && (
              <motion.div
                layoutId="clickedbutton"
                transition={{ type: 'spring', bounce: 0.3, duration: 0.6 }}
                className={cn(
                  'absolute inset-0 bg-gray-200 dark:bg-zinc-800 rounded-full',
                  activeTabClassName
                )}
              />
            )}

            <span className="relative block text-black dark:text-white">
              {tab.title}
            </span>
          </button>
        ))}
      </div>
      <FadeInDiv
        tabs={tabs}
        active={active}
        key={active.value}
        hovering={hovering}
        className={cn('mt-32', contentClassName)}
      />
    </>
  )
}

export const FadeInDiv = ({
  className,
  tabs,
  hovering,
}: {
  className?: string
  key?: string
  tabs: Tab[]
  active: Tab
  hovering?: boolean
}) => {
  const isActive = (tab: Tab) => {
    return tab.value === tabs[0].value
  }
  return (
    <div className="relative w-full h-full">
      {tabs.map((tab, idx) => (
        <motion.div
          key={tab.value}
          layoutId={tab.value}
          style={{
            scale: 1 - idx * 0.1,
            top: hovering ? idx * -50 : 0,
            zIndex: -idx,
            opacity: idx < 3 ? 1 - idx * 0.1 : 0,
          }}
          animate={{
            y: isActive(tab) ? [0, 40, 0] : 0,
          }}
          className={cn('w-full h-full absolute top-0 left-0', className)}
        >
          {tab.content}
        </motion.div>
      ))}
    </div>
  )
}

Infinite Moving Cards

Nieskończenie przewijające się karty - idealne do testimoniali.

TScomponents/ui/infinite-moving-cards.tsx
TypeScript
// components/ui/infinite-moving-cards.tsx
'use client'

import { cn } from '@/lib/utils'
import React, { useEffect, useState } from 'react'

export const InfiniteMovingCards = ({
  items,
  direction = 'left',
  speed = 'fast',
  pauseOnHover = true,
  className,
}: {
  items: {
    quote: string
    name: string
    title: string
  }[]
  direction?: 'left' | 'right'
  speed?: 'fast' | 'normal' | 'slow'
  pauseOnHover?: boolean
  className?: string
}) => {
  const containerRef = React.useRef<HTMLDivElement>(null)
  const scrollerRef = React.useRef<HTMLUListElement>(null)

  useEffect(() => {
    addAnimation()
  }, [])

  const [start, setStart] = useState(false)

  function addAnimation() {
    if (containerRef.current && scrollerRef.current) {
      const scrollerContent = Array.from(scrollerRef.current.children)

      scrollerContent.forEach((item) => {
        const duplicatedItem = item.cloneNode(true)
        if (scrollerRef.current) {
          scrollerRef.current.appendChild(duplicatedItem)
        }
      })

      getDirection()
      getSpeed()
      setStart(true)
    }
  }

  const getDirection = () => {
    if (containerRef.current) {
      if (direction === 'left') {
        containerRef.current.style.setProperty(
          '--animation-direction',
          'forwards'
        )
      } else {
        containerRef.current.style.setProperty(
          '--animation-direction',
          'reverse'
        )
      }
    }
  }

  const getSpeed = () => {
    if (containerRef.current) {
      if (speed === 'fast') {
        containerRef.current.style.setProperty('--animation-duration', '20s')
      } else if (speed === 'normal') {
        containerRef.current.style.setProperty('--animation-duration', '40s')
      } else {
        containerRef.current.style.setProperty('--animation-duration', '80s')
      }
    }
  }

  return (
    <div
      ref={containerRef}
      className={cn(
        'scroller relative z-20 max-w-7xl overflow-hidden [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)]',
        className
      )}
    >
      <ul
        ref={scrollerRef}
        className={cn(
          'flex min-w-full shrink-0 gap-4 py-4 w-max flex-nowrap',
          start && 'animate-scroll',
          pauseOnHover && 'hover:[animation-play-state:paused]'
        )}
      >
        {items.map((item, idx) => (
          <li
            className="w-[350px] max-w-full relative rounded-2xl border border-b-0 flex-shrink-0 border-slate-700 px-8 py-6 md:w-[450px]"
            style={{
              background:
                'linear-gradient(180deg, var(--slate-800), var(--slate-900)',
            }}
            key={item.name + idx}
          >
            <blockquote>
              <div
                aria-hidden="true"
                className="user-select-none -z-1 pointer-events-none absolute -left-0.5 -top-0.5 h-[calc(100%_+_4px)] w-[calc(100%_+_4px)]"
              ></div>
              <span className="relative z-20 text-sm leading-[1.6] text-gray-100 font-normal">
                {item.quote}
              </span>
              <div className="relative z-20 mt-6 flex flex-row items-center">
                <span className="flex flex-col gap-1">
                  <span className="text-sm leading-[1.6] text-gray-400 font-normal">
                    {item.name}
                  </span>
                  <span className="text-sm leading-[1.6] text-gray-400 font-normal">
                    {item.title}
                  </span>
                </span>
              </div>
            </blockquote>
          </li>
        ))}
      </ul>
    </div>
  )
}

Tworzenie kompletnej strony z Aceternity UI

Przykład Landing Page

TSapp/page.tsx
TypeScript
// app/page.tsx
import { Spotlight } from '@/components/ui/spotlight'
import { TextGenerateEffect } from '@/components/ui/text-generate-effect'
import { CardContainer, CardBody, CardItem } from '@/components/ui/3d-card'
import { InfiniteMovingCards } from '@/components/ui/infinite-moving-cards'
import { Meteors } from '@/components/ui/meteors'

const testimonials = [
  {
    quote: "This product changed the way we work. Absolutely amazing experience!",
    name: "Sarah Johnson",
    title: "CEO at TechCorp",
  },
  {
    quote: "Best investment we've made this year. The results speak for themselves.",
    name: "Michael Chen",
    title: "CTO at StartupXYZ",
  },
  {
    quote: "Incredible customer support and outstanding product quality.",
    name: "Emily Williams",
    title: "Product Manager at InnovateCo",
  },
]

const features = [
  {
    title: "Lightning Fast",
    description: "Optimized for speed with cutting-edge technology",
    icon: "⚡",
  },
  {
    title: "Secure by Default",
    description: "Enterprise-grade security built into every feature",
    icon: "🔒",
  },
  {
    title: "AI Powered",
    description: "Smart automation that learns and adapts to your needs",
    icon: "🤖",
  },
]

export default function LandingPage() {
  return (
    <main className="min-h-screen bg-black/[0.96] antialiased bg-grid-white/[0.02]">
      {/* Hero Section with Spotlight */}
      <section className="h-screen w-full flex md:items-center md:justify-center relative overflow-hidden">
        <Spotlight
          className="-top-40 left-0 md:left-60 md:-top-20"
          fill="white"
        />
        <div className="p-4 max-w-7xl mx-auto relative z-10 w-full pt-20 md:pt-0">
          <h1 className="text-4xl md:text-7xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-b from-neutral-50 to-neutral-400">
            Build Amazing
            <br />
            Products Faster
          </h1>
          <TextGenerateEffect
            words="Transform your ideas into reality with our cutting-edge platform.
                   Start building today and see results tomorrow."
            className="mt-8 text-center max-w-2xl mx-auto"
          />
          <div className="flex gap-4 justify-center mt-10">
            <button className="px-8 py-3 rounded-full bg-white text-black font-semibold hover:bg-gray-200 transition">
              Get Started
            </button>
            <button className="px-8 py-3 rounded-full border border-white/20 text-white hover:bg-white/10 transition">
              Learn More
            </button>
          </div>
        </div>
      </section>

      {/* Features Section with 3D Cards */}
      <section className="py-20 px-4">
        <h2 className="text-3xl md:text-5xl font-bold text-center text-white mb-16">
          Powerful Features
        </h2>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
          {features.map((feature, idx) => (
            <CardContainer key={idx} className="inter-var">
              <CardBody className="bg-gray-900 relative group/card border-white/[0.1] w-full h-auto rounded-xl p-6 border">
                <CardItem
                  translateZ="50"
                  className="text-4xl mb-4"
                >
                  {feature.icon}
                </CardItem>
                <CardItem
                  translateZ="60"
                  className="text-xl font-bold text-white"
                >
                  {feature.title}
                </CardItem>
                <CardItem
                  as="p"
                  translateZ="80"
                  className="text-neutral-400 text-sm mt-2"
                >
                  {feature.description}
                </CardItem>
              </CardBody>
            </CardContainer>
          ))}
        </div>
      </section>

      {/* Testimonials Section */}
      <section className="py-20 relative">
        <h2 className="text-3xl md:text-5xl font-bold text-center text-white mb-16">
          What Our Customers Say
        </h2>
        <InfiniteMovingCards
          items={testimonials}
          direction="right"
          speed="slow"
        />
      </section>

      {/* CTA Section with Meteors */}
      <section className="py-20 px-4 relative overflow-hidden">
        <div className="max-w-2xl mx-auto relative">
          <div className="absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] rounded-full blur-3xl" />
          <div className="relative shadow-xl bg-gray-900 border border-gray-800 px-8 py-12 rounded-2xl overflow-hidden">
            <h2 className="text-3xl font-bold text-white text-center mb-4">
              Ready to Get Started?
            </h2>
            <p className="text-neutral-400 text-center mb-8">
              Join thousands of satisfied customers today.
            </p>
            <div className="flex justify-center">
              <button className="px-8 py-3 rounded-full bg-gradient-to-r from-blue-500 to-teal-500 text-white font-semibold hover:opacity-90 transition">
                Start Free Trial
              </button>
            </div>
            <Meteors number={20} />
          </div>
        </div>
      </section>
    </main>
  )
}

Best practices

1. Optymalizacja wydajności

Code
TypeScript
// Lazy loading komponentów wizualnych
import dynamic from 'next/dynamic'

const Spotlight = dynamic(
  () => import('@/components/ui/spotlight').then((mod) => mod.Spotlight),
  { ssr: false }
)

const Meteors = dynamic(
  () => import('@/components/ui/meteors').then((mod) => mod.Meteors),
  { ssr: false }
)

2. Responsywność

Code
TypeScript
// Dostosowanie efektów do urządzeń mobilnych
export function ResponsiveSpotlight() {
  const isMobile = useMediaQuery('(max-width: 768px)')

  return (
    <div className="relative">
      {/* Spotlight tylko na desktop */}
      {!isMobile && (
        <Spotlight className="-top-40 left-0 md:left-60 md:-top-20" fill="white" />
      )}
      <div className="relative z-10">
        {/* Content */}
      </div>
    </div>
  )
}

3. Accessibility

Code
TypeScript
// Dodanie reduced motion support
const prefersReducedMotion = usePrefersReducedMotion()

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{
    duration: prefersReducedMotion ? 0 : 0.8,
  }}
>
  {children}
</motion.div>

FAQ - Najczęściej zadawane pytania

Czy Aceternity UI jest darmowe?

Tak, Aceternity UI jest całkowicie darmowe i open-source. Wszystkie komponenty można kopiować i używać w projektach komercyjnych bez żadnych opłat.

Jakie są wymagania do użycia Aceternity UI?

Potrzebujesz projektu React z Tailwind CSS i Framer Motion. Komponenty są zaprojektowane dla Next.js, ale działają też z Create React App i Vite.

Czy komponenty działają z Server Components?

Większość komponentów Aceternity UI wymaga 'use client' ze względu na animacje Framer Motion. Możesz je importować do Server Components jako Client Component wrapper.

Jak dostosować kolory i style?

Komponenty używają Tailwind CSS, więc możesz łatwo modyfikować klasy bezpośrednio w kodzie. Kolory są często definiowane jako CSS variables lub gradienty.

Czy mogę używać Aceternity UI z TypeScript?

Tak, wszystkie komponenty mają pełne wsparcie TypeScript z typami props.

Jak rozwiązać problemy z animacjami?

Upewnij się, że Framer Motion jest zainstalowany i że komponent ma 'use client' directive. Sprawdź też czy Tailwind config zawiera potrzebne keyframes.

Czy komponenty są responsywne?

Tak, komponenty są zaprojektowane mobile-first z Tailwind breakpoints. Niektóre efekty 3D mogą być wyłączone na mobile dla wydajności.

Cennik

PlanCenaZawartość
Open SourceBezpłatneWszystkie komponenty, copy-paste
Pro Templates$49-199Gotowe szablony landing pages
Custom DevelopmentNa zapytanieDedykowane komponenty

Podsumowanie

Aceternity UI to potężna kolekcja komponentów React z efektami 3D i animacjami:

  • 50+ komponentów z efektami wizualnymi
  • Framer Motion dla płynnych animacji 60fps
  • Tailwind CSS dla łatwej customizacji
  • TypeScript z pełnym wsparciem typów
  • Copy-paste approach bez dodatkowych zależności
  • Dark mode out of the box

Idealne do tworzenia spektakularnych landing pages i stron marketingowych.


Aceternity UI - React components with 3D effects and animations

What is Aceternity UI?

Aceternity UI is a collection of free, open-source React components designed for creating spectacular visual effects. The library offers ready-to-use components with advanced 3D animations, parallax effects, glows, spotlight effects, and many other visual effects that give applications and websites a professional, modern look.

Aceternity UI was created in response to the growing demand from developers for easily accessible, high-quality visual components. The creators of the library gathered the most commonly seen effects from modern tech company websites and made them available as copy-paste components that can be easily adapted to your own needs.

The library is built on the foundations of Tailwind CSS and Framer Motion, which ensures excellent animation performance and ease of customization. The components are fully responsive and support dark mode out of the box.

Why Aceternity UI?

Key advantages

  1. Copy-Paste Approach - Every component is ready-made code to copy
  2. Framer Motion - Smooth, performant 60fps animations
  3. Tailwind CSS - Full control over styles
  4. TypeScript - Full type support
  5. Zero Dependencies - Only Tailwind and Framer Motion
  6. Dark Mode - Native dark mode support
  7. Responsiveness - Mobile-first design
  8. Documentation - Detailed usage examples

Aceternity UI vs Magic UI vs shadcn/ui

FeatureAceternity UIMagic UIshadcn/ui
Focus3D/visual effectsMicro animationsForm components
AnimationsVery advancedAdvancedBasic
3D EffectsYes, main focusPartiallyNo
Copy-pasteYesYesYes
TailwindYesYesYes
Framer MotionRequiredRequiredOptional
Number of components50+100+50+
Best forLanding pagesMarketingApplications

Installation and configuration

Required dependencies

Code
Bash
npm install framer-motion clsx tailwind-merge

npm install @tabler/icons-react
npm install simplex-noise

Tailwind CSS configuration

JStailwind.config.js
JavaScript
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      animation: {
        spotlight: 'spotlight 2s ease .75s 1 forwards',
        shimmer: 'shimmer 2s linear infinite',
        'meteor-effect': 'meteor 5s linear infinite',
        aurora: 'aurora 60s linear infinite',
        'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
      },
      keyframes: {
        spotlight: {
          '0%': {
            opacity: 0,
            transform: 'translate(-72%, -62%) scale(0.5)',
          },
          '100%': {
            opacity: 1,
            transform: 'translate(-50%,-40%) scale(1)',
          },
        },
        shimmer: {
          from: {
            backgroundPosition: '0 0',
          },
          to: {
            backgroundPosition: '-200% 0',
          },
        },
        meteor: {
          '0%': { transform: 'rotate(215deg) translateX(0)', opacity: 1 },
          '70%': { opacity: 1 },
          '100%': {
            transform: 'rotate(215deg) translateX(-500px)',
            opacity: 0,
          },
        },
        aurora: {
          from: {
            backgroundPosition: '50% 50%, 50% 50%',
          },
          to: {
            backgroundPosition: '350% 50%, 350% 50%',
          },
        },
        'border-beam': {
          '100%': {
            'offset-distance': '100%',
          },
        },
      },
    },
  },
  plugins: [],
}

The cn() utility function

TSlib/utils.ts
TypeScript
// lib/utils.ts
import { ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Popular Aceternity UI components

3D Card Effect

A card with a 3D follow-mouse effect - the most iconic component of the library.

TScomponents/ui/3d-card.tsx
TypeScript
// components/ui/3d-card.tsx
'use client'

import { cn } from '@/lib/utils'
import React, {
  createContext,
  useState,
  useContext,
  useRef,
  useEffect,
} from 'react'

const MouseEnterContext = createContext<
  [boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined
>(undefined)

export const CardContainer = ({
  children,
  className,
  containerClassName,
}: {
  children?: React.ReactNode
  className?: string
  containerClassName?: string
}) => {
  const containerRef = useRef<HTMLDivElement>(null)
  const [isMouseEntered, setIsMouseEntered] = useState(false)

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!containerRef.current) return
    const { left, top, width, height } =
      containerRef.current.getBoundingClientRect()
    const x = (e.clientX - left - width / 2) / 25
    const y = (e.clientY - top - height / 2) / 25
    containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`
  }

  const handleMouseEnter = () => {
    setIsMouseEntered(true)
  }

  const handleMouseLeave = () => {
    if (!containerRef.current) return
    setIsMouseEntered(false)
    containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`
  }

  return (
    <MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
      <div
        className={cn(
          'py-20 flex items-center justify-center',
          containerClassName
        )}
        style={{
          perspective: '1000px',
        }}
      >
        <div
          ref={containerRef}
          onMouseEnter={handleMouseEnter}
          onMouseMove={handleMouseMove}
          onMouseLeave={handleMouseLeave}
          className={cn(
            'flex items-center justify-center relative transition-all duration-200 ease-linear',
            className
          )}
          style={{
            transformStyle: 'preserve-3d',
          }}
        >
          {children}
        </div>
      </div>
    </MouseEnterContext.Provider>
  )
}

export const CardBody = ({
  children,
  className,
}: {
  children: React.ReactNode
  className?: string
}) => {
  return (
    <div
      className={cn(
        'h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d]',
        className
      )}
    >
      {children}
    </div>
  )
}

export const CardItem = ({
  as: Tag = 'div',
  children,
  className,
  translateX = 0,
  translateY = 0,
  translateZ = 0,
  rotateX = 0,
  rotateY = 0,
  rotateZ = 0,
  ...rest
}: {
  as?: React.ElementType
  children: React.ReactNode
  className?: string
  translateX?: number | string
  translateY?: number | string
  translateZ?: number | string
  rotateX?: number | string
  rotateY?: number | string
  rotateZ?: number | string
  [key: string]: unknown
}) => {
  const ref = useRef<HTMLDivElement>(null)
  const [isMouseEntered] = useMouseEnter()

  useEffect(() => {
    handleAnimations()
  }, [isMouseEntered])

  const handleAnimations = () => {
    if (!ref.current) return
    if (isMouseEntered) {
      ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`
    } else {
      ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`
    }
  }

  return (
    <Tag
      ref={ref}
      className={cn('w-fit transition duration-200 ease-linear', className)}
      {...rest}
    >
      {children}
    </Tag>
  )
}

export const useMouseEnter = () => {
  const context = useContext(MouseEnterContext)
  if (context === undefined) {
    throw new Error('useMouseEnter must be used within a MouseEnterProvider')
  }
  return context
}

3D Card usage example:

Code
TypeScript
import { CardContainer, CardBody, CardItem } from '@/components/ui/3d-card'
import Image from 'next/image'

export function ThreeDCardDemo() {
  return (
    <CardContainer className="inter-var">
      <CardBody className="bg-gray-50 relative group/card dark:hover:shadow-2xl dark:hover:shadow-emerald-500/[0.1] dark:bg-black dark:border-white/[0.2] border-black/[0.1] w-auto sm:w-[30rem] h-auto rounded-xl p-6 border">
        <CardItem
          translateZ="50"
          className="text-xl font-bold text-neutral-600 dark:text-white"
        >
          Make things float in air
        </CardItem>
        <CardItem
          as="p"
          translateZ="60"
          className="text-neutral-500 text-sm max-w-sm mt-2 dark:text-neutral-300"
        >
          Hover over this card to unleash the power of CSS perspective
        </CardItem>
        <CardItem translateZ="100" className="w-full mt-4">
          <Image
            src="/images/product.png"
            height="1000"
            width="1000"
            className="h-60 w-full object-cover rounded-xl group-hover/card:shadow-xl"
            alt="thumbnail"
          />
        </CardItem>
        <div className="flex justify-between items-center mt-20">
          <CardItem
            translateZ={20}
            as="button"
            className="px-4 py-2 rounded-xl text-xs font-normal dark:text-white"
          >
            Try now →
          </CardItem>
          <CardItem
            translateZ={20}
            as="button"
            className="px-4 py-2 rounded-xl bg-black dark:bg-white dark:text-black text-white text-xs font-bold"
          >
            Sign up
          </CardItem>
        </div>
      </CardBody>
    </CardContainer>
  )
}

Spotlight Effect

A cursor-following spotlight effect - great for hero sections.

TScomponents/ui/spotlight.tsx
TypeScript
// components/ui/spotlight.tsx
'use client'

import React from 'react'
import { cn } from '@/lib/utils'

type SpotlightProps = {
  className?: string
  fill?: string
}

export const Spotlight = ({ className, fill }: SpotlightProps) => {
  return (
    <svg
      className={cn(
        'animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%] opacity-0',
        className
      )}
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 3787 2842"
      fill="none"
    >
      <g filter="url(#filter)">
        <ellipse
          cx="1924.71"
          cy="273.501"
          rx="1924.71"
          ry="273.501"
          transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
          fill={fill || 'white'}
          fillOpacity="0.21"
        ></ellipse>
      </g>
      <defs>
        <filter
          id="filter"
          x="0.860352"
          y="0.838989"
          width="3785.16"
          height="2840.26"
          filterUnits="userSpaceOnUse"
          colorInterpolationFilters="sRGB"
        >
          <feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood>
          <feBlend
            mode="normal"
            in="SourceGraphic"
            in2="BackgroundImageFix"
            result="shape"
          ></feBlend>
          <feGaussianBlur
            stdDeviation="151"
            result="effect1_foregroundBlur_1065_8"
          ></feGaussianBlur>
        </filter>
      </defs>
    </svg>
  )
}

Spotlight usage example:

Code
TypeScript
import { Spotlight } from '@/components/ui/spotlight'

export function SpotlightPreview() {
  return (
    <div className="h-[40rem] w-full rounded-md flex md:items-center md:justify-center bg-black/[0.96] antialiased bg-grid-white/[0.02] relative overflow-hidden">
      <Spotlight
        className="-top-40 left-0 md:left-60 md:-top-20"
        fill="white"
      />
      <div className="p-4 max-w-7xl mx-auto relative z-10 w-full pt-20 md:pt-0">
        <h1 className="text-4xl md:text-7xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-b from-neutral-50 to-neutral-400 bg-opacity-50">
          Spotlight <br /> is the new trend.
        </h1>
        <p className="mt-4 font-normal text-base text-neutral-300 max-w-lg text-center mx-auto">
          Spotlight effect is a great way to draw attention to a specific part
          of the page. Here, we are drawing the attention towards the text
          section of the page. I don&apos;t know what else to write so I&apos;ll
          just keep writing gibberish.
        </p>
      </div>
    </div>
  )
}

Meteors Effect

Falling meteors in the background - a space-inspired visual effect.

TScomponents/ui/meteors.tsx
TypeScript
// components/ui/meteors.tsx
'use client'

import { cn } from '@/lib/utils'
import React from 'react'

export const Meteors = ({
  number,
  className,
}: {
  number?: number
  className?: string
}) => {
  const meteors = new Array(number || 20).fill(true)
  return (
    <>
      {meteors.map((_, idx) => (
        <span
          key={'meteor' + idx}
          className={cn(
            'animate-meteor-effect absolute top-1/2 left-1/2 h-0.5 w-0.5 rounded-[9999px] bg-slate-500 shadow-[0_0_0_1px_#ffffff10] rotate-[215deg]',
            "before:content-[''] before:absolute before:top-1/2 before:-translate-y-[50%] before:w-[50px] before:h-[1px] before:bg-gradient-to-r before:from-[#64748b] before:to-transparent",
            className
          )}
          style={{
            top: 0,
            left: Math.floor(Math.random() * (400 - -400) + -400) + 'px',
            animationDelay: Math.random() * (0.8 - 0.2) + 0.2 + 's',
            animationDuration: Math.floor(Math.random() * (10 - 2) + 2) + 's',
          }}
        ></span>
      ))}
    </>
  )
}

Meteors usage example:

Code
TypeScript
import { Meteors } from '@/components/ui/meteors'

export function MeteorsDemo() {
  return (
    <div className="w-full relative max-w-xs">
      <div className="absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] bg-red-500 rounded-full blur-3xl" />
      <div className="relative shadow-xl bg-gray-900 border border-gray-800 px-4 py-8 h-full overflow-hidden rounded-2xl flex flex-col justify-end items-start">
        <div className="h-5 w-5 rounded-full border flex items-center justify-center mb-4 border-gray-500">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth="1.5"
            stroke="currentColor"
            className="h-2 w-2 text-gray-300"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M4.5 4.5l15 15m0 0V8.25m0 11.25H8.25"
            />
          </svg>
        </div>

        <h1 className="font-bold text-xl text-white mb-4 relative z-50">
          Meteors because they&apos;re cool
        </h1>

        <p className="font-normal text-base text-slate-500 mb-4 relative z-50">
          I don&apos;t know what to write so I&apos;ll just paste something cool
          here. One more sentence because lorem ipsum is boring.
        </p>

        <button className="border px-4 py-1 rounded-lg border-gray-500 text-gray-300">
          Explore
        </button>

        <Meteors number={20} />
      </div>
    </div>
  )
}

Aurora Background

An animated aurora borealis background - a northern lights effect.

TScomponents/ui/aurora-background.tsx
TypeScript
// components/ui/aurora-background.tsx
'use client'

import { cn } from '@/lib/utils'
import React, { ReactNode } from 'react'

interface AuroraBackgroundProps extends React.HTMLProps<HTMLDivElement> {
  children: ReactNode
  showRadialGradient?: boolean
}

export const AuroraBackground = ({
  className,
  children,
  showRadialGradient = true,
  ...props
}: AuroraBackgroundProps) => {
  return (
    <main>
      <div
        className={cn(
          'relative flex flex-col h-[100vh] items-center justify-center bg-zinc-50 dark:bg-zinc-900 text-slate-950 transition-bg',
          className
        )}
        {...props}
      >
        <div className="absolute inset-0 overflow-hidden">
          <div
            className={cn(
              `
            [--white-gradient:repeating-linear-gradient(100deg,var(--white)_0%,var(--white)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--white)_16%)]
            [--dark-gradient:repeating-linear-gradient(100deg,var(--black)_0%,var(--black)_7%,var(--transparent)_10%,var(--transparent)_12%,var(--black)_16%)]
            [--aurora:repeating-linear-gradient(100deg,var(--blue-500)_10%,var(--indigo-300)_15%,var(--blue-300)_20%,var(--violet-200)_25%,var(--blue-400)_30%)]
            [background-image:var(--white-gradient),var(--aurora)]
            dark:[background-image:var(--dark-gradient),var(--aurora)]
            [background-size:300%,_200%]
            [background-position:50%_50%,50%_50%]
            filter blur-[10px] invert dark:invert-0
            after:content-[""] after:absolute after:inset-0 after:[background-image:var(--white-gradient),var(--aurora)]
            after:dark:[background-image:var(--dark-gradient),var(--aurora)]
            after:[background-size:200%,_100%]
            after:animate-aurora after:[background-attachment:fixed] after:mix-blend-difference
            pointer-events-none
            absolute -inset-[10px] opacity-50 will-change-transform`,
              showRadialGradient &&
                `[mask-image:radial-gradient(ellipse_at_100%_0%,black_10%,var(--transparent)_70%)]`
            )}
          ></div>
        </div>
        {children}
      </div>
    </main>
  )
}

Aurora Background usage example:

Code
TypeScript
'use client'

import { motion } from 'framer-motion'
import React from 'react'
import { AuroraBackground } from '@/components/ui/aurora-background'

export function AuroraBackgroundDemo() {
  return (
    <AuroraBackground>
      <motion.div
        initial={{ opacity: 0.0, y: 40 }}
        whileInView={{ opacity: 1, y: 0 }}
        transition={{
          delay: 0.3,
          duration: 0.8,
          ease: 'easeInOut',
        }}
        className="relative flex flex-col gap-4 items-center justify-center px-4"
      >
        <div className="text-3xl md:text-7xl font-bold dark:text-white text-center">
          Background lights are cool you know.
        </div>
        <div className="font-extralight text-base md:text-4xl dark:text-neutral-200 py-4">
          And this, is chemical burn.
        </div>
        <button className="bg-black dark:bg-white rounded-full w-fit text-white dark:text-black px-4 py-2">
          Debug now
        </button>
      </motion.div>
    </AuroraBackground>
  )
}

Sparkles Effect

Animated sparkles around an element - a subtle magical effect.

TScomponents/ui/sparkles.tsx
TypeScript
// components/ui/sparkles.tsx
'use client'

import React, { useId, useMemo } from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

interface SparkleProps {
  id: string
  createdAt: number
  color: string
  size: number
  style: {
    top: string
    left: string
    zIndex: number
  }
}

interface SparklesProps {
  className?: string
  children: React.ReactNode
  sparklesCount?: number
  colors?: {
    first: string
    second: string
  }
}

const DEFAULT_SPARKLE_COLORS = {
  first: '#9E7AFF',
  second: '#FE8BBB',
}

const generateSparkle = (colors = DEFAULT_SPARKLE_COLORS): SparkleProps => {
  return {
    id: String(Math.random()),
    createdAt: Date.now(),
    color: Math.random() > 0.5 ? colors.first : colors.second,
    size: Math.random() * 10 + 10,
    style: {
      top: Math.random() * 100 + '%',
      left: Math.random() * 100 + '%',
      zIndex: 2,
    },
  }
}

const Sparkle = ({ color, size, style }: Omit<SparkleProps, 'id' | 'createdAt'>) => {
  return (
    <motion.svg
      width={size}
      height={size}
      viewBox="0 0 160 160"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      style={style}
      className="absolute pointer-events-none"
      initial={{ scale: 0, rotate: 0 }}
      animate={{
        scale: [0, 1, 0],
        rotate: [0, 180],
      }}
      transition={{
        duration: 1.5,
        repeat: Infinity,
        repeatDelay: Math.random() * 2,
      }}
    >
      <path
        d="M80 0C80 0 84.2846 41.2925 101.496 58.504C118.707 75.7154 160 80 160 80C160 80 118.707 84.2846 101.496 101.496C84.2846 118.707 80 160 80 160C80 160 75.7154 118.707 58.504 101.496C41.2925 84.2846 0 80 0 80C0 80 41.2925 75.7154 58.504 58.504C75.7154 41.2925 80 0 80 0Z"
        fill={color}
      />
    </motion.svg>
  )
}

export const Sparkles: React.FC<SparklesProps> = ({
  children,
  className,
  sparklesCount = 10,
  colors = DEFAULT_SPARKLE_COLORS,
}) => {
  const sparkles = useMemo(() => {
    return Array.from({ length: sparklesCount }, () => generateSparkle(colors))
  }, [sparklesCount, colors])

  return (
    <span className={cn('relative inline-block', className)}>
      {sparkles.map((sparkle) => (
        <Sparkle
          key={sparkle.id}
          color={sparkle.color}
          size={sparkle.size}
          style={sparkle.style}
        />
      ))}
      <span className="relative z-10">{children}</span>
    </span>
  )
}

Sparkles usage example:

Code
TypeScript
import { Sparkles } from '@/components/ui/sparkles'

export function SparklesPreview() {
  return (
    <div className="h-40 w-full bg-black flex items-center justify-center">
      <Sparkles
        sparklesCount={12}
        colors={{
          first: '#A07CFE',
          second: '#FE8FB5',
        }}
      >
        <span className="text-4xl font-bold text-white">
          Magical Text
        </span>
      </Sparkles>
    </div>
  )
}

Lamp Effect

A glowing lamp with animated light - a dramatic hero effect.

TScomponents/ui/lamp.tsx
TypeScript
// components/ui/lamp.tsx
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export const LampContainer = ({
  children,
  className,
}: {
  children: React.ReactNode
  className?: string
}) => {
  return (
    <div
      className={cn(
        'relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-slate-950 w-full rounded-md z-0',
        className
      )}
    >
      <div className="relative flex w-full flex-1 scale-y-125 items-center justify-center isolate z-0">
        <motion.div
          initial={{ opacity: 0.5, width: '15rem' }}
          whileInView={{ opacity: 1, width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          style={{
            backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
          }}
          className="absolute inset-auto right-1/2 h-56 overflow-visible w-[30rem] bg-gradient-conic from-cyan-500 via-transparent to-transparent text-white [--conic-position:from_70deg_at_center_top]"
        >
          <div className="absolute w-[100%] left-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
          <div className="absolute w-40 h-[100%] left-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_right,white,transparent)]" />
        </motion.div>
        <motion.div
          initial={{ opacity: 0.5, width: '15rem' }}
          whileInView={{ opacity: 1, width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          style={{
            backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
          }}
          className="absolute inset-auto left-1/2 h-56 w-[30rem] bg-gradient-conic from-transparent via-transparent to-cyan-500 text-white [--conic-position:from_290deg_at_center_top]"
        >
          <div className="absolute w-40 h-[100%] right-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_left,white,transparent)]" />
          <div className="absolute w-[100%] right-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
        </motion.div>
        <div className="absolute top-1/2 h-48 w-full translate-y-12 scale-x-150 bg-slate-950 blur-2xl"></div>
        <div className="absolute top-1/2 z-50 h-48 w-full bg-transparent opacity-10 backdrop-blur-md"></div>
        <div className="absolute inset-auto z-50 h-36 w-[28rem] -translate-y-1/2 rounded-full bg-cyan-500 opacity-50 blur-3xl"></div>
        <motion.div
          initial={{ width: '8rem' }}
          whileInView={{ width: '16rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          className="absolute inset-auto z-30 h-36 w-64 -translate-y-[6rem] rounded-full bg-cyan-400 blur-2xl"
        ></motion.div>
        <motion.div
          initial={{ width: '15rem' }}
          whileInView={{ width: '30rem' }}
          transition={{
            delay: 0.3,
            duration: 0.8,
            ease: 'easeInOut',
          }}
          className="absolute inset-auto z-50 h-0.5 w-[30rem] -translate-y-[7rem] bg-cyan-400"
        ></motion.div>

        <div className="absolute inset-auto z-40 h-44 w-full -translate-y-[12.5rem] bg-slate-950"></div>
      </div>

      <div className="relative z-50 flex -translate-y-80 flex-col items-center px-5">
        {children}
      </div>
    </div>
  )
}

Lamp Effect usage example:

Code
TypeScript
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { LampContainer } from '@/components/ui/lamp'

export function LampDemo() {
  return (
    <LampContainer>
      <motion.h1
        initial={{ opacity: 0.5, y: 100 }}
        whileInView={{ opacity: 1, y: 0 }}
        transition={{
          delay: 0.3,
          duration: 0.8,
          ease: 'easeInOut',
        }}
        className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4 bg-clip-text text-center text-4xl font-medium tracking-tight text-transparent md:text-7xl"
      >
        Build lamps <br /> the right way
      </motion.h1>
    </LampContainer>
  )
}

Background Beams

Animated light beams in the background.

TScomponents/ui/background-beams.tsx
TypeScript
// components/ui/background-beams.tsx
'use client'

import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export const BackgroundBeams = ({ className }: { className?: string }) => {
  const paths = [
    'M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875',
    'M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867',
    'M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859',
    'M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851',
    'M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843',
    'M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835',
    'M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827',
    'M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819',
    'M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811',
    'M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803',
  ]

  return (
    <div
      className={cn(
        'absolute h-full w-full inset-0 [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center',
        className
      )}
    >
      <svg
        className="z-0 h-full w-full pointer-events-none absolute"
        width="100%"
        height="100%"
        viewBox="0 0 696 316"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        {paths.map((path, index) => (
          <motion.path
            key={`path-` + index}
            d={path}
            stroke={`url(#linearGradient-${index})`}
            strokeOpacity="0.4"
            strokeWidth="0.5"
          ></motion.path>
        ))}
        <defs>
          {paths.map((_, index) => (
            <motion.linearGradient
              id={`linearGradient-${index}`}
              key={`gradient-${index}`}
              initial={{
                x1: '0%',
                x2: '0%',
                y1: '0%',
                y2: '0%',
              }}
              animate={{
                x1: ['0%', '100%'],
                x2: ['0%', '95%'],
                y1: ['0%', '100%'],
                y2: ['0%', `${93 + Math.random() * 8}%`],
              }}
              transition={{
                duration: Math.random() * 10 + 10,
                ease: 'easeInOut',
                repeat: Infinity,
                delay: Math.random() * 10,
              }}
            >
              <stop stopColor="#18CCFC" stopOpacity="0"></stop>
              <stop stopColor="#18CCFC"></stop>
              <stop offset="32.5%" stopColor="#6344F5"></stop>
              <stop offset="100%" stopColor="#AE48FF" stopOpacity="0"></stop>
            </motion.linearGradient>
          ))}
        </defs>
      </svg>
    </div>
  )
}

Text Generate Effect

Animated text generation letter by letter.

TScomponents/ui/text-generate-effect.tsx
TypeScript
// components/ui/text-generate-effect.tsx
'use client'

import { useEffect } from 'react'
import { motion, stagger, useAnimate } from 'framer-motion'
import { cn } from '@/lib/utils'

export const TextGenerateEffect = ({
  words,
  className,
  filter = true,
  duration = 0.5,
}: {
  words: string
  className?: string
  filter?: boolean
  duration?: number
}) => {
  const [scope, animate] = useAnimate()
  const wordsArray = words.split(' ')

  useEffect(() => {
    animate(
      'span',
      {
        opacity: 1,
        filter: filter ? 'blur(0px)' : 'none',
      },
      {
        duration: duration ? duration : 1,
        delay: stagger(0.2),
      }
    )
  }, [scope.current])

  const renderWords = () => {
    return (
      <motion.div ref={scope}>
        {wordsArray.map((word, idx) => {
          return (
            <motion.span
              key={word + idx}
              className="dark:text-white text-black opacity-0"
              style={{
                filter: filter ? 'blur(10px)' : 'none',
              }}
            >
              {word}{' '}
            </motion.span>
          )
        })}
      </motion.div>
    )
  }

  return (
    <div className={cn('font-bold', className)}>
      <div className="mt-4">
        <div className="dark:text-white text-black text-2xl leading-snug tracking-wide">
          {renderWords()}
        </div>
      </div>
    </div>
  )
}

Text Generate Effect usage example:

Code
TypeScript
import { TextGenerateEffect } from '@/components/ui/text-generate-effect'

const words = `Oxygen gets you high. In a catastrophic emergency, we're taking giant, panicked breaths. Suddenly you become euphoric, docile. You accept your fate.`

export function TextGenerateEffectDemo() {
  return <TextGenerateEffect words={words} />
}

Advanced components

Animated Tabs

TScomponents/ui/animated-tabs.tsx
TypeScript
// components/ui/animated-tabs.tsx
'use client'

import { useState } from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

type Tab = {
  title: string
  value: string
  content?: string | React.ReactNode
}

export const Tabs = ({
  tabs: propTabs,
  containerClassName,
  activeTabClassName,
  tabClassName,
  contentClassName,
}: {
  tabs: Tab[]
  containerClassName?: string
  activeTabClassName?: string
  tabClassName?: string
  contentClassName?: string
}) => {
  const [active, setActive] = useState<Tab>(propTabs[0])
  const [tabs, setTabs] = useState<Tab[]>(propTabs)

  const moveSelectedTabToTop = (idx: number) => {
    const newTabs = [...propTabs]
    const selectedTab = newTabs.splice(idx, 1)
    newTabs.unshift(selectedTab[0])
    setTabs(newTabs)
    setActive(newTabs[0])
  }

  const [hovering, setHovering] = useState(false)

  return (
    <>
      <div
        className={cn(
          'flex flex-row items-center justify-start [perspective:1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar max-w-full w-full',
          containerClassName
        )}
      >
        {propTabs.map((tab, idx) => (
          <button
            key={tab.title}
            onClick={() => {
              moveSelectedTabToTop(idx)
            }}
            onMouseEnter={() => setHovering(true)}
            onMouseLeave={() => setHovering(false)}
            className={cn('relative px-4 py-2 rounded-full', tabClassName)}
            style={{
              transformStyle: 'preserve-3d',
            }}
          >
            {active.value === tab.value && (
              <motion.div
                layoutId="clickedbutton"
                transition={{ type: 'spring', bounce: 0.3, duration: 0.6 }}
                className={cn(
                  'absolute inset-0 bg-gray-200 dark:bg-zinc-800 rounded-full',
                  activeTabClassName
                )}
              />
            )}

            <span className="relative block text-black dark:text-white">
              {tab.title}
            </span>
          </button>
        ))}
      </div>
      <FadeInDiv
        tabs={tabs}
        active={active}
        key={active.value}
        hovering={hovering}
        className={cn('mt-32', contentClassName)}
      />
    </>
  )
}

export const FadeInDiv = ({
  className,
  tabs,
  hovering,
}: {
  className?: string
  key?: string
  tabs: Tab[]
  active: Tab
  hovering?: boolean
}) => {
  const isActive = (tab: Tab) => {
    return tab.value === tabs[0].value
  }
  return (
    <div className="relative w-full h-full">
      {tabs.map((tab, idx) => (
        <motion.div
          key={tab.value}
          layoutId={tab.value}
          style={{
            scale: 1 - idx * 0.1,
            top: hovering ? idx * -50 : 0,
            zIndex: -idx,
            opacity: idx < 3 ? 1 - idx * 0.1 : 0,
          }}
          animate={{
            y: isActive(tab) ? [0, 40, 0] : 0,
          }}
          className={cn('w-full h-full absolute top-0 left-0', className)}
        >
          {tab.content}
        </motion.div>
      ))}
    </div>
  )
}

Infinite Moving Cards

Infinitely scrolling cards - perfect for testimonials.

TScomponents/ui/infinite-moving-cards.tsx
TypeScript
// components/ui/infinite-moving-cards.tsx
'use client'

import { cn } from '@/lib/utils'
import React, { useEffect, useState } from 'react'

export const InfiniteMovingCards = ({
  items,
  direction = 'left',
  speed = 'fast',
  pauseOnHover = true,
  className,
}: {
  items: {
    quote: string
    name: string
    title: string
  }[]
  direction?: 'left' | 'right'
  speed?: 'fast' | 'normal' | 'slow'
  pauseOnHover?: boolean
  className?: string
}) => {
  const containerRef = React.useRef<HTMLDivElement>(null)
  const scrollerRef = React.useRef<HTMLUListElement>(null)

  useEffect(() => {
    addAnimation()
  }, [])

  const [start, setStart] = useState(false)

  function addAnimation() {
    if (containerRef.current && scrollerRef.current) {
      const scrollerContent = Array.from(scrollerRef.current.children)

      scrollerContent.forEach((item) => {
        const duplicatedItem = item.cloneNode(true)
        if (scrollerRef.current) {
          scrollerRef.current.appendChild(duplicatedItem)
        }
      })

      getDirection()
      getSpeed()
      setStart(true)
    }
  }

  const getDirection = () => {
    if (containerRef.current) {
      if (direction === 'left') {
        containerRef.current.style.setProperty(
          '--animation-direction',
          'forwards'
        )
      } else {
        containerRef.current.style.setProperty(
          '--animation-direction',
          'reverse'
        )
      }
    }
  }

  const getSpeed = () => {
    if (containerRef.current) {
      if (speed === 'fast') {
        containerRef.current.style.setProperty('--animation-duration', '20s')
      } else if (speed === 'normal') {
        containerRef.current.style.setProperty('--animation-duration', '40s')
      } else {
        containerRef.current.style.setProperty('--animation-duration', '80s')
      }
    }
  }

  return (
    <div
      ref={containerRef}
      className={cn(
        'scroller relative z-20 max-w-7xl overflow-hidden [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)]',
        className
      )}
    >
      <ul
        ref={scrollerRef}
        className={cn(
          'flex min-w-full shrink-0 gap-4 py-4 w-max flex-nowrap',
          start && 'animate-scroll',
          pauseOnHover && 'hover:[animation-play-state:paused]'
        )}
      >
        {items.map((item, idx) => (
          <li
            className="w-[350px] max-w-full relative rounded-2xl border border-b-0 flex-shrink-0 border-slate-700 px-8 py-6 md:w-[450px]"
            style={{
              background:
                'linear-gradient(180deg, var(--slate-800), var(--slate-900)',
            }}
            key={item.name + idx}
          >
            <blockquote>
              <div
                aria-hidden="true"
                className="user-select-none -z-1 pointer-events-none absolute -left-0.5 -top-0.5 h-[calc(100%_+_4px)] w-[calc(100%_+_4px)]"
              ></div>
              <span className="relative z-20 text-sm leading-[1.6] text-gray-100 font-normal">
                {item.quote}
              </span>
              <div className="relative z-20 mt-6 flex flex-row items-center">
                <span className="flex flex-col gap-1">
                  <span className="text-sm leading-[1.6] text-gray-400 font-normal">
                    {item.name}
                  </span>
                  <span className="text-sm leading-[1.6] text-gray-400 font-normal">
                    {item.title}
                  </span>
                </span>
              </div>
            </blockquote>
          </li>
        ))}
      </ul>
    </div>
  )
}

Building a complete page with Aceternity UI

Landing page example

TSapp/page.tsx
TypeScript
// app/page.tsx
import { Spotlight } from '@/components/ui/spotlight'
import { TextGenerateEffect } from '@/components/ui/text-generate-effect'
import { CardContainer, CardBody, CardItem } from '@/components/ui/3d-card'
import { InfiniteMovingCards } from '@/components/ui/infinite-moving-cards'
import { Meteors } from '@/components/ui/meteors'

const testimonials = [
  {
    quote: "This product changed the way we work. Absolutely amazing experience!",
    name: "Sarah Johnson",
    title: "CEO at TechCorp",
  },
  {
    quote: "Best investment we've made this year. The results speak for themselves.",
    name: "Michael Chen",
    title: "CTO at StartupXYZ",
  },
  {
    quote: "Incredible customer support and outstanding product quality.",
    name: "Emily Williams",
    title: "Product Manager at InnovateCo",
  },
]

const features = [
  {
    title: "Lightning Fast",
    description: "Optimized for speed with cutting-edge technology",
    icon: "⚡",
  },
  {
    title: "Secure by Default",
    description: "Enterprise-grade security built into every feature",
    icon: "🔒",
  },
  {
    title: "AI Powered",
    description: "Smart automation that learns and adapts to your needs",
    icon: "🤖",
  },
]

export default function LandingPage() {
  return (
    <main className="min-h-screen bg-black/[0.96] antialiased bg-grid-white/[0.02]">
      {/* Hero Section with Spotlight */}
      <section className="h-screen w-full flex md:items-center md:justify-center relative overflow-hidden">
        <Spotlight
          className="-top-40 left-0 md:left-60 md:-top-20"
          fill="white"
        />
        <div className="p-4 max-w-7xl mx-auto relative z-10 w-full pt-20 md:pt-0">
          <h1 className="text-4xl md:text-7xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-b from-neutral-50 to-neutral-400">
            Build Amazing
            <br />
            Products Faster
          </h1>
          <TextGenerateEffect
            words="Transform your ideas into reality with our cutting-edge platform.
                   Start building today and see results tomorrow."
            className="mt-8 text-center max-w-2xl mx-auto"
          />
          <div className="flex gap-4 justify-center mt-10">
            <button className="px-8 py-3 rounded-full bg-white text-black font-semibold hover:bg-gray-200 transition">
              Get Started
            </button>
            <button className="px-8 py-3 rounded-full border border-white/20 text-white hover:bg-white/10 transition">
              Learn More
            </button>
          </div>
        </div>
      </section>

      {/* Features Section with 3D Cards */}
      <section className="py-20 px-4">
        <h2 className="text-3xl md:text-5xl font-bold text-center text-white mb-16">
          Powerful Features
        </h2>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
          {features.map((feature, idx) => (
            <CardContainer key={idx} className="inter-var">
              <CardBody className="bg-gray-900 relative group/card border-white/[0.1] w-full h-auto rounded-xl p-6 border">
                <CardItem
                  translateZ="50"
                  className="text-4xl mb-4"
                >
                  {feature.icon}
                </CardItem>
                <CardItem
                  translateZ="60"
                  className="text-xl font-bold text-white"
                >
                  {feature.title}
                </CardItem>
                <CardItem
                  as="p"
                  translateZ="80"
                  className="text-neutral-400 text-sm mt-2"
                >
                  {feature.description}
                </CardItem>
              </CardBody>
            </CardContainer>
          ))}
        </div>
      </section>

      {/* Testimonials Section */}
      <section className="py-20 relative">
        <h2 className="text-3xl md:text-5xl font-bold text-center text-white mb-16">
          What Our Customers Say
        </h2>
        <InfiniteMovingCards
          items={testimonials}
          direction="right"
          speed="slow"
        />
      </section>

      {/* CTA Section with Meteors */}
      <section className="py-20 px-4 relative overflow-hidden">
        <div className="max-w-2xl mx-auto relative">
          <div className="absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] rounded-full blur-3xl" />
          <div className="relative shadow-xl bg-gray-900 border border-gray-800 px-8 py-12 rounded-2xl overflow-hidden">
            <h2 className="text-3xl font-bold text-white text-center mb-4">
              Ready to Get Started?
            </h2>
            <p className="text-neutral-400 text-center mb-8">
              Join thousands of satisfied customers today.
            </p>
            <div className="flex justify-center">
              <button className="px-8 py-3 rounded-full bg-gradient-to-r from-blue-500 to-teal-500 text-white font-semibold hover:opacity-90 transition">
                Start Free Trial
              </button>
            </div>
            <Meteors number={20} />
          </div>
        </div>
      </section>
    </main>
  )
}

Best practices

1. Performance optimization

Code
TypeScript
import dynamic from 'next/dynamic'

const Spotlight = dynamic(
  () => import('@/components/ui/spotlight').then((mod) => mod.Spotlight),
  { ssr: false }
)

const Meteors = dynamic(
  () => import('@/components/ui/meteors').then((mod) => mod.Meteors),
  { ssr: false }
)

2. Responsiveness

Code
TypeScript
export function ResponsiveSpotlight() {
  const isMobile = useMediaQuery('(max-width: 768px)')

  return (
    <div className="relative">
      {!isMobile && (
        <Spotlight className="-top-40 left-0 md:left-60 md:-top-20" fill="white" />
      )}
      <div className="relative z-10">
        {/* Content */}
      </div>
    </div>
  )
}

3. Accessibility

Code
TypeScript
const prefersReducedMotion = usePrefersReducedMotion()

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{
    duration: prefersReducedMotion ? 0 : 0.8,
  }}
>
  {children}
</motion.div>

FAQ - frequently asked questions

Is Aceternity UI free?

Yes, Aceternity UI is completely free and open-source. All components can be copied and used in commercial projects without any fees.

What are the requirements for using Aceternity UI?

You need a React project with Tailwind CSS and Framer Motion. The components are designed for Next.js, but they also work with Create React App and Vite.

Do the components work with Server Components?

Most Aceternity UI components require 'use client' due to Framer Motion animations. You can import them into Server Components as a Client Component wrapper.

How do I customize colors and styles?

The components use Tailwind CSS, so you can easily modify classes directly in the code. Colors are often defined as CSS variables or gradients.

Can I use Aceternity UI with TypeScript?

Yes, all components have full TypeScript support with prop types.

How do I troubleshoot animation issues?

Make sure Framer Motion is installed and that the component has the 'use client' directive. Also check that your Tailwind config contains the necessary keyframes.

Are the components responsive?

Yes, the components are designed mobile-first with Tailwind breakpoints. Some 3D effects may be disabled on mobile for performance reasons.

Pricing

PlanPriceContents
Open SourceFreeAll components, copy-paste
Pro Templates$49-199Ready-made landing page templates
Custom DevelopmentOn requestDedicated components

Summary

Aceternity UI is a powerful collection of React components with 3D effects and animations:

  • 50+ components with visual effects
  • Framer Motion for smooth 60fps animations
  • Tailwind CSS for easy customization
  • TypeScript with full type support
  • Copy-paste approach with no additional dependencies
  • Dark mode out of the box

Perfect for creating spectacular landing pages and marketing websites.