reactnextjsui-librariescomponentstutorial

How to Use React Components from Different UI Library Sites

A comprehensive guide to combining components from Magic UI, Eldora UI, React Bits, and other shadcn-style libraries in your Next.js projects

January 26, 202515 min read
How to Use React Components from Different UI Library Sites

The Big Picture: What These Libraries Actually Are

Modern UI libraries like Magic UI, Eldora UI, and React Bits follow the shadcn/ui pattern. This is fundamentally different from traditional npm packages:

comparison

Key insight: These libraries don't ship compiled packages. They provide the source code that becomes part of YOUR project.

This means:

  • Components are copied directly into your codebase
  • You own and control the code completely
  • No hidden dependencies or version conflicts
  • Full customization freedom

Components We'll Use

In this tutorial, we'll combine three components from different libraries:

  1. Hacker Background - Matrix-style falling characters animation Eldora UI
  2. Terminal - MacOS-style terminal with typing animations Magic uI
  3. Electric Border (React Bits) - Animated border with SVG turbulence effects React Bits

How to Get Component Source Code

When you visit a component page, you typically see:

  • Preview - Live demo of the effect
  • Usage code - How to import and use it
  • Installation command - CLI command
  • Props - Available configuration options

But where's the actual component code? Here are three methods:

Method 1: Use the CLI (Easiest)

CLI using

npx shadcn@latest add @eldoraui/hacker-background

This command:

  1. Connects to the library's registry (e.g., eldoraui.site/r/hacker-background)
  2. Downloads the component source code
  3. Places it in your project (usually components/eldoraui/)

Tip: After running the CLI, check the new file to see the actual source code.

Method 2: Click the "Manual" Tab

Manual using

Many component sites have a "Manual" tab in the Installation section that reveals the full source code you can copy directly.

Method 3: Find the GitHub Repository

GitHub using

and specific GitHub repo:

GitHub specific repo

Every reputable component library is open source. Find the repo and navigate to the source:

Eldora UI:

  • GitHub: github.com/karthikmudunuri/eldoraui
  • Path: apps/www/registry/eldoraui/hacker-background.tsx

Magic UI:

  • GitHub: github.com/magicuidesign/magicui
  • Path: registry/magicui/terminal.tsx

Project Setup (One-Time)

Step 1: Create Next.js Project

npx create-next-app@latest my-project --typescript --tailwind --eslint
cd my-project

Step 2: Initialize shadcn/ui

npx shadcn@latest init

Step 3: Install Motion Library

Most animated components require the motion library:

npm install motion

Step 4: Create the Utility Function

Create lib/utils.ts:

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

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

Install dependencies:

npm install clsx tailwind-merge

Note: The cn() utility is not always necessary. Some components use simple template literals instead.

For example, the official Eldora UI code uses a simple template literal:

// Official Eldora UI - simple concatenation
className={`pointer-events-none ${className}`}

if cn( ) utility was required, then the code would look like this:

className={cn("absolute inset-0 h-full w-full", className)}

cn need or not

Folder Structure for Multiple Libraries

Folder structure

Organize components by their source library:

my-project/
├── components/
│   ├── ui/                    # shadcn/ui & Magic UI
│   │   └── terminal.tsx
│   ├── eldoraui/              # Eldora UI
│   │   └── hacker-background.tsx
│   └── reactbits/             # React Bits
│       └── electric-border.tsx
└── lib/
    └── utils.ts

Practical Example: Hacker Background

Let's add the Hacker Background component from Eldora UI step by step.

Step 1: Try the CLI

npx shadcn@latest add @eldoraui/hacker-background

If successful, check components/eldoraui/hacker-background.tsx - the source is there!

Step 2: If CLI Fails, Go Manual

  1. Visit: https://eldoraui.site/docs/components/hacker-background
  2. Scroll to "Installation"
  3. Click the "Manual" tab
  4. Copy the entire code
  5. Create components/eldoraui/hacker-background.tsx
  6. Paste the code

Step 3: Fix Import Paths

The copied code might have:

import { cn } from "@/lib/utils"

Make sure this matches YOUR project's utility file location.

Step 4: Component Implementation

components/eldoraui/hacker-background.tsx:

"use client"

import React, { useEffect, useRef } from "react"

interface HackerBackgroundProps {
  color?: string
  fontSize?: number
  className?: string
  speed?: number
}

export const HackerBackground: React.FC<HackerBackgroundProps> = ({
  color = "#0F0",
  fontSize = 14,
  className = "",
  speed = 1,
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    const canvas = canvasRef.current
    if (!canvas) return

    const ctx = canvas.getContext("2d")
    if (!ctx) return

    const resizeCanvas = () => {
      canvas.width = window.innerWidth
      canvas.height = window.innerHeight
    }

    resizeCanvas()
    window.addEventListener("resize", resizeCanvas)

    let animationFrameId: number
    const columns = Math.floor(canvas.width / fontSize)
    const drops: number[] = new Array(columns).fill(1)

    const chars =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()_+"

    let lastTime = 0
    const interval = 33 // ~30 fps

    const draw = (currentTime: number) => {
      animationFrameId = requestAnimationFrame(draw)

      if (currentTime - lastTime < interval) return
      lastTime = currentTime

      ctx.fillStyle = "rgba(0, 0, 0, 0.05)"
      ctx.fillRect(0, 0, canvas.width, canvas.height)

      ctx.fillStyle = color
      ctx.font = `${fontSize}px monospace`

      for (let i = 0; i < drops.length; i++) {
        const text = chars[Math.floor(Math.random() * chars.length)]
        ctx.fillText(text, i * fontSize, drops[i] * fontSize)

        if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
          drops[i] = 0
        }

        drops[i] += speed // Use the speed prop to control fall rate
      }
    }

    animationFrameId = requestAnimationFrame(draw)

    return () => {
      window.removeEventListener("resize", resizeCanvas)
      cancelAnimationFrame(animationFrameId)
    }
  }, [color, fontSize, speed])

  return (
    <canvas
      ref={canvasRef}
      className={`pointer-events-none ${className}`}
      style={{
        position: "absolute",
        top: 0,
        left: 0,
        width: "100%",
        height: "100%",
      }}
    />
  )
}

The code for components/reactbits/electric-border.tsx:

"use client"

import React, { 
  CSSProperties, 
  PropsWithChildren, 
  useEffect, 
  useId, 
  useLayoutEffect, 
  useRef 
} from "react"

type ElectricBorderProps = PropsWithChildren<{
  color?: string
  speed?: number
  chaos?: number
  thickness?: number
  className?: string
  style?: CSSProperties
}>

function hexToRgba(hex: string, alpha = 1): string {
  if (!hex) return `rgba(0,0,0,${alpha})`
  let h = hex.replace("#", "")
  if (h.length === 3) {
    h = h
      .split("")
      .map((c) => c + c)
      .join("")
  }
  const int = parseInt(h, 16)
  const r = (int >> 16) & 255
  const g = (int >> 8) & 255
  const b = int & 255
  return `rgba(${r}, ${g}, ${b}, ${alpha})`
}

const ElectricBorder: React.FC<ElectricBorderProps> = ({
  children,
  color = "#5227FF",
  speed = 1,
  chaos = 1,
  thickness = 2,
  className,
  style,
}) => {
  const rawId = useId().replace(/[:]/g, "")
  const filterId = `turbulent-displace-${rawId}`
  const svgRef = useRef<SVGSVGElement | null>(null)
  const rootRef = useRef<HTMLDivElement | null>(null)
  const strokeRef = useRef<HTMLDivElement | null>(null)

  const updateAnim = () => {
    const svg = svgRef.current
    const host = rootRef.current
    if (!svg || !host) return

    if (strokeRef.current) {
      strokeRef.current.style.filter = `url(#${filterId})`
    }

    const width = Math.max(
      1,
      Math.round(host.clientWidth || host.getBoundingClientRect().width || 0)
    )
    const height = Math.max(
      1,
      Math.round(host.clientHeight || host.getBoundingClientRect().height || 0)
    )

    const dyAnims = Array.from(
      svg.querySelectorAll<SVGAnimateElement>(
        'feOffset > animate[attributeName="dy"]'
      )
    )
    if (dyAnims.length >= 2) {
      dyAnims[0].setAttribute("values", `${height}; 0`)
      dyAnims[1].setAttribute("values", `0; -${height}`)
    }

    const dxAnims = Array.from(
      svg.querySelectorAll<SVGAnimateElement>(
        'feOffset > animate[attributeName="dx"]'
      )
    )
    if (dxAnims.length >= 2) {
      dxAnims[0].setAttribute("values", `${width}; 0`)
      dxAnims[1].setAttribute("values", `0; -${width}`)
    }

    const baseDur = 6
    const dur = Math.max(0.001, baseDur / (speed || 1))
    ;[...dyAnims, ...dxAnims].forEach((a) => a.setAttribute("dur", `${dur}s`))

    const disp = svg.querySelector("feDisplacementMap")
    if (disp) disp.setAttribute("scale", String(30 * (chaos || 1)))

    const filterEl = svg.querySelector<SVGFilterElement>(
      `#${CSS.escape(filterId)}`
    )
    if (filterEl) {
      filterEl.setAttribute("x", "-200%")
      filterEl.setAttribute("y", "-200%")
      filterEl.setAttribute("width", "500%")
      filterEl.setAttribute("height", "500%")
    }

    requestAnimationFrame(() => {
      ;[...dyAnims, ...dxAnims].forEach((a: SVGAnimateElement) => {
        if (typeof a.beginElement === "function") {
          try {
            a.beginElement()
          } catch {
            // Ignore errors
          }
        }
      })
    })
  }

  useEffect(() => {
    updateAnim()
  }, [speed, chaos])

  useLayoutEffect(() => {
    if (!rootRef.current) return
    const ro = new ResizeObserver(() => updateAnim())
    ro.observe(rootRef.current)
    updateAnim()
    return () => ro.disconnect()
  }, [])

  const inheritRadius: CSSProperties = {
    borderRadius: style?.borderRadius ?? "inherit",
  }

  const strokeStyle: CSSProperties = {
    ...inheritRadius,
    borderWidth: thickness,
    borderStyle: "solid",
    borderColor: color,
  }

  const glow1Style: CSSProperties = {
    ...inheritRadius,
    borderWidth: thickness,
    borderStyle: "solid",
    borderColor: hexToRgba(color, 0.6),
    filter: `blur(${0.5 + thickness * 0.25}px)`,
    opacity: 0.5,
  }

  const glow2Style: CSSProperties = {
    ...inheritRadius,
    borderWidth: thickness,
    borderStyle: "solid",
    borderColor: color,
    filter: `blur(${2 + thickness * 0.5}px)`,
    opacity: 0.5,
  }

  const bgGlowStyle: CSSProperties = {
    ...inheritRadius,
    transform: "scale(1.08)",
    filter: "blur(32px)",
    opacity: 0.3,
    zIndex: -1,
    background: `linear-gradient(-30deg, ${hexToRgba(color, 0.8)}, transparent, ${color})`,
  }

  return (
    <div
      ref={rootRef}
      className={"relative isolate " + (className ?? "")}
      style={style}
    >
      <svg
        ref={svgRef}
        className="fixed -left-[10000px] -top-[10000px] h-[10px] w-[10px] pointer-events-none opacity-[0.001]"
        aria-hidden
        focusable="false"
      >
        <defs>
          <filter
            id={filterId}
            colorInterpolationFilters="sRGB"
            x="-20%"
            y="-20%"
            width="140%"
            height="140%"
          >
            <feTurbulence
              type="turbulence"
              baseFrequency="0.02"
              numOctaves="10"
              result="noise1"
              seed="1"
            />
            <feOffset in="noise1" dx="0" dy="0" result="offsetNoise1">
              <animate
                attributeName="dy"
                values="700; 0"
                dur="6s"
                repeatCount="indefinite"
                calcMode="linear"
              />
            </feOffset>

            <feTurbulence
              type="turbulence"
              baseFrequency="0.02"
              numOctaves="10"
              result="noise2"
              seed="1"
            />
            <feOffset in="noise2" dx="0" dy="0" result="offsetNoise2">
              <animate
                attributeName="dy"
                values="0; -700"
                dur="6s"
                repeatCount="indefinite"
                calcMode="linear"
              />
            </feOffset>

            <feTurbulence
              type="turbulence"
              baseFrequency="0.02"
              numOctaves="10"
              result="noise1"
              seed="2"
            />
            <feOffset in="noise1" dx="0" dy="0" result="offsetNoise3">
              <animate
                attributeName="dx"
                values="490; 0"
                dur="6s"
                repeatCount="indefinite"
                calcMode="linear"
              />
            </feOffset>

            <feTurbulence
              type="turbulence"
              baseFrequency="0.02"
              numOctaves="10"
              result="noise2"
              seed="2"
            />
            <feOffset in="noise2" dx="0" dy="0" result="offsetNoise4">
              <animate
                attributeName="dx"
                values="0; -490"
                dur="6s"
                repeatCount="indefinite"
                calcMode="linear"
              />
            </feOffset>

            <feComposite in="offsetNoise1" in2="offsetNoise2" result="part1" />
            <feComposite in="offsetNoise3" in2="offsetNoise4" result="part2" />
            <feBlend
              in="part1"
              in2="part2"
              mode="color-dodge"
              result="combinedNoise"
            />
            <feDisplacementMap
              in="SourceGraphic"
              in2="combinedNoise"
              scale="30"
              xChannelSelector="R"
              yChannelSelector="B"
            />
          </filter>
        </defs>
      </svg>

      <div
        className="absolute inset-0 pointer-events-none"
        style={inheritRadius}
      >
        <div
          ref={strokeRef}
          className="absolute inset-0 box-border"
          style={strokeStyle}
        />
        <div className="absolute inset-0 box-border" style={glow1Style} />
        <div className="absolute inset-0 box-border" style={glow2Style} />
        <div className="absolute inset-0" style={bgGlowStyle} />
      </div>

      <div className="relative" style={inheritRadius}>
        {children}
      </div>
    </div>
  )
}

export default ElectricBorder

The code for components/ui/terminal.tsx:

"use client"

import {
  Children,
  createContext,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react"
import { motion, MotionProps, useInView } from "motion/react"

import { cn } from "@/lib/utils"

interface SequenceContextValue {
  completeItem: (index: number) => void
  activeIndex: number
  sequenceStarted: boolean
}

const SequenceContext = createContext<SequenceContextValue | null>(null)

const useSequence = () => useContext(SequenceContext)

const ItemIndexContext = createContext<number | null>(null)
const useItemIndex = () => useContext(ItemIndexContext)

interface AnimatedSpanProps extends MotionProps {
  children: React.ReactNode
  delay?: number
  className?: string
  startOnView?: boolean
}

export const AnimatedSpan = ({
  children,
  delay = 0,
  className,
  startOnView = false,
  ...props
}: AnimatedSpanProps) => {
  const elementRef = useRef<HTMLDivElement | null>(null)
  const isInView = useInView(elementRef as React.RefObject<Element>, {
    amount: 0.3,
    once: true,
  })

  const sequence = useSequence()
  const itemIndex = useItemIndex()
  const [hasStarted, setHasStarted] = useState(false)

  useEffect(() => {
    if (!sequence || itemIndex === null) return
    if (!sequence.sequenceStarted) return
    if (hasStarted) return
    if (sequence.activeIndex === itemIndex) {
      setHasStarted(true)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sequence?.activeIndex, sequence?.sequenceStarted, hasStarted, itemIndex])

  const shouldAnimate = sequence ? hasStarted : startOnView ? isInView : true

  return (
    <motion.div
      ref={elementRef}
      initial={{ opacity: 0, y: -5 }}
      animate={shouldAnimate ? { opacity: 1, y: 0 } : { opacity: 0, y: -5 }}
      transition={{ duration: 0.3, delay: sequence ? 0 : delay / 1000 }}
      className={cn("grid text-sm font-normal tracking-tight", className)}
      onAnimationComplete={() => {
        if (!sequence) return
        if (itemIndex === null) return
        sequence.completeItem(itemIndex)
      }}
      {...props}
    >
      {children}
    </motion.div>
  )
}

interface TypingAnimationProps extends MotionProps {
  children: string
  className?: string
  duration?: number
  delay?: number
  as?: React.ElementType
  startOnView?: boolean
}

export const TypingAnimation = ({
  children,
  className,
  duration = 60,
  delay = 0,
  as: Component = "span",
  startOnView = true,
  ...props
}: TypingAnimationProps) => {
  if (typeof children !== "string") {
    throw new Error("TypingAnimation: children must be a string.")
  }

  const MotionComponent = useMemo(
    () =>
      motion.create(Component, {
        forwardMotionProps: true,
      }),
    [Component]
  )

  const [displayedText, setDisplayedText] = useState<string>("")
  const [started, setStarted] = useState(false)
  const elementRef = useRef<HTMLElement | null>(null)
  const isInView = useInView(elementRef as React.RefObject<Element>, {
    amount: 0.3,
    once: true,
  })

  const sequence = useSequence()
  const itemIndex = useItemIndex()

  // Store sequence.completeItem in a ref to avoid dependency issues
  const completeItemRef = useRef(sequence?.completeItem)
  completeItemRef.current = sequence?.completeItem

  useEffect(() => {
    if (sequence && itemIndex !== null) {
      if (!sequence.sequenceStarted) return
      if (started) return
      if (sequence.activeIndex === itemIndex) {
        setStarted(true)
      }
      return
    }

    if (!startOnView) {
      const startTimeout = setTimeout(() => setStarted(true), delay)
      return () => clearTimeout(startTimeout)
    }

    if (!isInView) return

    const startTimeout = setTimeout(() => setStarted(true), delay)
    return () => clearTimeout(startTimeout)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    delay,
    startOnView,
    isInView,
    started,
    sequence?.activeIndex,
    sequence?.sequenceStarted,
    itemIndex,
  ])

  useEffect(() => {
    if (!started) return

    let i = 0
    const typingEffect = setInterval(() => {
      if (i < children.length) {
        setDisplayedText(children.substring(0, i + 1))
        i++
      } else {
        clearInterval(typingEffect)
        // Use ref to call completeItem without adding sequence to deps
        if (completeItemRef.current && itemIndex !== null) {
          completeItemRef.current(itemIndex)
        }
      }
    }, duration)

    return () => {
      clearInterval(typingEffect)
    }
    // Intentionally omitting sequence from deps to prevent restart on sequence change
    
  }, [children, duration, started, itemIndex])

  return (
    <MotionComponent
      ref={elementRef}
      className={cn("text-sm font-normal tracking-tight", className)}
      {...props}
    >
      {displayedText}
    </MotionComponent>
  )
}

interface TerminalProps {
  children: React.ReactNode
  className?: string
  sequence?: boolean
  startOnView?: boolean
}

export const Terminal = ({
  children,
  className,
  sequence = true,
  startOnView = true,
}: TerminalProps) => {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const isInView = useInView(containerRef as React.RefObject<Element>, {
    amount: 0.3,
    once: true,
  })

  const [activeIndex, setActiveIndex] = useState(0)
  const sequenceHasStarted = sequence ? !startOnView || isInView : false

  const contextValue = useMemo<SequenceContextValue | null>(() => {
    if (!sequence) return null
    return {
      completeItem: (index: number) => {
        setActiveIndex((current) => (index === current ? current + 1 : current))
      },
      activeIndex,
      sequenceStarted: sequenceHasStarted,
    }
  }, [sequence, activeIndex, sequenceHasStarted])

  const wrappedChildren = useMemo(() => {
    if (!sequence) return children
    const array = Children.toArray(children)
    return array.map((child, index) => (
      <ItemIndexContext.Provider key={index} value={index}>
        {child as React.ReactNode}
      </ItemIndexContext.Provider>
    ))
  }, [children, sequence])

  const content = (
    <div
      ref={containerRef}
      className={cn(
        "border-border bg-background z-0 h-full max-h-[400px] w-full max-w-lg rounded-xl border",
        className
      )}
    >
      <div className="border-border flex flex-col gap-y-2 border-b p-4">
        <div className="flex flex-row gap-x-2">
          <div className="h-2 w-2 rounded-full bg-red-500"></div>
          <div className="h-2 w-2 rounded-full bg-yellow-500"></div>
          <div className="h-2 w-2 rounded-full bg-green-500"></div>
        </div>
      </div>
      <pre className="p-4">
        <code className="grid gap-y-1 overflow-auto">{wrappedChildren}</code>
      </pre>
    </div>
  )

  if (!sequence) return content

  return (
    <SequenceContext.Provider value={contextValue}>
      {content}
    </SequenceContext.Provider>
  )
}

Step 5: Use the Component

import { HackerBackground } from "@/components/eldoraui/hacker-background"

export default function Page() {
  return (
    <div className="relative h-screen bg-black">
      <HackerBackground 
        color="#22c55e"
        fontSize={14}
        speed={0.5}  // Lower = slower falling
      />
    </div>
  )
}

What If There's No Source Code Visible?

Sometimes a library doesn't show the full source on the website. Here are your options:

Option A: Check GitHub

Find the repository and navigate to the registry folder:

github.com/[owner]/[repo]/tree/main/registry/[library-name]/

Option B: Run CLI and Inspect

Run the CLI command, then open the created file in your editor to see the source code.

Option C: Create a Compatible Implementation

If you understand the component's API and visual effect, you can create your own implementation:

// Compatible implementation based on Eldora UI API
// Original: https://eldoraui.site/docs/components/hacker-background

export const HackerBackground = ({
  color = "#22c55e",
  fontSize = 16,
  speed = 1,
  className,
}) => {
  // Your Canvas-based Matrix rain implementation
}

Important: Always note the original source in a comment when creating compatible implementations.

Step 6: Combining Multiple Libraries

Once you have components from different sources, use them together:

// page.tsx
import { Terminal, TypingAnimation, AnimatedSpan } from "@/components/ui/terminal"
import { HackerBackground } from "@/components/eldoraui/hacker-background"
import ElectricBorder from "@/components/reactbits/electric-border"

export default function Home() {
  return (
    <div className="relative min-h-screen bg-black text-white overflow-hidden">
      {/* Matrix Background Layer */}
      <HackerBackground
        color="#22c55e"
        fontSize={14}
        speed={0.5}
        className="opacity-50"
      />

      {/* Content Layer */}
      <div className="relative z-10 flex flex-col items-center justify-center min-h-screen p-6 gap-8">
        {/* Hero Section with Electric Border */}
        <ElectricBorder
          color="#22c55e"
          speed={1.2}
          chaos={0.6}
          thickness={2}
          style={{ borderRadius: 20 }}
        >
          <div className="bg-zinc-900/80 backdrop-blur-md p-8 rounded-[20px]">
            <h1 className="text-3xl md:text-5xl font-bold mb-3 bg-linear-to-r from-green-400 to-emerald-500 bg-clip-text text-transparent">
              Component Fusion
            </h1>
            <p className="text-zinc-400 text-lg">
              Three UI libraries working together seamlessly.
            </p>
          </div>
        </ElectricBorder>

        {/* Terminal Demo */}
        <Terminal className="bg-zinc-900/90 backdrop-blur-sm border-zinc-700 shadow-2xl shadow-green-500/20">
          <TypingAnimation>&gt; pnpm dlx shadcn@latest init</TypingAnimation>
          <AnimatedSpan className="text-green-500">
            ✔ Preflight checks.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Verifying framework. Found Next.js.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Validating Tailwind CSS.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Validating import alias.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Writing components.json.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Checking registry.
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Updating tailwind.config.ts
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Updating app/globals.css
          </AnimatedSpan>
          <AnimatedSpan className="text-green-500">
            ✔ Installing dependencies.
          </AnimatedSpan>
          <AnimatedSpan className="text-blue-500">
            <span>ℹ Updated 1 file:</span>
            <span className="pl-2">- lib/utils.ts</span>
          </AnimatedSpan>
          <TypingAnimation className="text-muted-foreground">
            Success! Project initialization completed.
          </TypingAnimation>
          <TypingAnimation className="text-muted-foreground">
            You may now add components.
          </TypingAnimation>
        </Terminal>

        {/* Feature Cards */}
        <div className="grid md:grid-cols-3 gap-4 max-w-3xl w-full">
          <ElectricBorder
            color="#22c55e"
            speed={0.8}
            chaos={0.4}
            thickness={1}
            style={{ borderRadius: 12 }}
          >
            <div className="bg-zinc-900/70 backdrop-blur-sm p-5 rounded-[12px] h-full">
              <div className="text-xs text-zinc-500 mb-1">Eldora UI</div>
              <h3 className="font-semibold text-lg mb-1">HackerBackground</h3>
              <p className="text-zinc-400 text-sm">
                Matrix-style falling characters
              </p>
            </div>
          </ElectricBorder>

          <ElectricBorder
            color="#3b82f6"
            speed={0.8}
            chaos={0.4}
            thickness={1}
            style={{ borderRadius: 12 }}
          >
            <div className="bg-zinc-900/70 backdrop-blur-sm p-5 rounded-[12px] h-full">
              <div className="text-xs text-zinc-500 mb-1">Magic UI</div>
              <h3 className="font-semibold text-lg mb-1">Terminal</h3>
              <p className="text-zinc-400 text-sm">Animated typing sequences</p>
            </div>
          </ElectricBorder>

          <ElectricBorder
            color="#a855f7"
            speed={0.8}
            chaos={0.4}
            thickness={1}
            style={{ borderRadius: 12 }}
          >
            <div className="bg-zinc-900/70 backdrop-blur-sm p-5 rounded-[12px] h-full">
              <div className="text-xs text-zinc-500 mb-1">React Bits</div>
              <h3 className="font-semibold text-lg mb-1">ElectricBorder</h3>
              <p className="text-zinc-400 text-sm">
                SVG turbulence glow effects
              </p>
            </div>
          </ElectricBorder>
        </div>

        {/* Footer note */}
        <div className="text-center text-zinc-500 text-sm max-w-md mt-4">
          <p>
            📁 Components are{" "}
            <span className="text-zinc-300 font-medium">copied</span> into your
            project
          </p>
          <p className="mt-1">No node_modules dependency - you own the code!</p>
        </div>
      </div>
    </div>
  );
}

Common Issues and Solutions

"Module not found" Error

Your import path doesn't match your file location. Check your file structure:

// Using @/ alias (if configured)
import { cn } from "@/lib/utils"

// Using relative paths
import { cn } from "../../lib/utils"

Component Not Animating

Make sure:

  1. "use client" is at the top of the component file
  2. Motion is installed: npm install motion
  3. Check browser console for errors

ESLint Warnings

Original library code may have React Hook dependency issues. Fix by:

  • Adding missing dependencies to useEffect arrays
  • Using useCallback for function references
  • Adding // eslint-disable-next-line if intentional

Tip: For motion import issues in ESLint, update eslint.config.js varsIgnorePattern to '^(motion|[A-Z_])'

The 3-Step Process

  1. Find the source - Use CLI, Manual tab, or GitHub
  2. Copy to your project - Create the file in your components folder
  3. Fix imports - Adjust paths to match your project structure

That's it! The beauty of the shadcn pattern is that you own the code completely. Mix components from any library, modify them freely, and never worry about dependency conflicts.

Resources