Home/Components/Loaders/Circuit Board Loader - React Electronics Loading Animation
Code · Live Preview
Componenttsx
// src/components/CircuitLoader/CircuitLoader.tsx
import { motion, useReducedMotion } from "framer-motion";
import { useId } from "react";

type CircuitLoaderProps = {
  className?: string;
  /** Width and height in px */
  size?: number;
  /** Seconds for a pulse to travel from a trace's start into the chip */
  speed?: number;
  /** Accent color for the current pulses and the bolt — use your brand color */
  color?: string;
  label?: string;
};

// viewBox is 200 x 200, the chip sits in the middle (72 → 128)
// Traces for ONE side (top). The same traces are rotated 90° for each side.
const TRACES = [
  "M 44 16 H 72 L 94 38 V 62",
  "M 106 8 V 62",
  "M 164 22 H 140 L 118 44 V 62",
];

// Where each trace starts — drawn as a small ring (a "via" on a real board)
const VIAS = [
  [44, 16],
  [106, 8],
  [164, 22],
];

// Chip pins along one side
const PINS = [82, 94, 106, 118];

const SIDES = [0, 90, 180, 270];

// Order the 12 pulses fire in, so they look random but stay evenly spaced
const FIRE_ORDER = [0, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2, 7];

// Lightning bolt in the center of the chip
const BOLT = "M 104 85 L 91 102 H 100 L 96 115 L 109 98 H 100 Z";

export function CircuitLoader({
  className = "",
  size = 180,
  speed = 1.6,
  color = "#22D3EE",
  label = "Loading",
}: CircuitLoaderProps) {
  const uid = useId().replace(/:/g, "");
  const reduce = useReducedMotion();
  const slot = speed / FIRE_ORDER.length;

  return (
    <div
      className={`flex flex-col items-center gap-4 ${className}`}
      role="status"
      aria-label={label || "Loading"}
    >
      <div
        className="relative aspect-square"
        style={{ width: size, maxWidth: "100%" }}
      >
        <svg
          viewBox="0 0 200 200"
          className="h-full w-full overflow-visible"
          fill="none"
          aria-hidden="true"
        >
          <defs>
            {/* Tight glow for the pulses and bolt */}
            <filter
              id={`circuit-glow-${uid}`}
              x="-50%"
              y="-50%"
              width="200%"
              height="200%"
            >
              <feGaussianBlur stdDeviation="2.4" result="blur" />
              <feMerge>
                <feMergeNode in="blur" />
                <feMergeNode in="SourceGraphic" />
              </feMerge>
            </filter>

            {/* Wide, soft halo behind the chip */}
            <filter
              id={`circuit-halo-${uid}`}
              x="-100%"
              y="-100%"
              width="300%"
              height="300%"
            >
              <feGaussianBlur stdDeviation="10" />
            </filter>
          </defs>

          {/* Board: traces, vias, and pins on all four sides */}
          {SIDES.map((deg) => (
            <g key={`board-${deg}`} transform={`rotate(${deg} 100 100)`}>
              {TRACES.map((d) => (
                <path
                  key={d}
                  d={d}
                  stroke="currentColor"
                  strokeOpacity={0.14}
                  strokeWidth={2.2}
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              ))}
              {VIAS.map(([cx, cy]) => (
                <circle
                  key={`${cx}-${cy}`}
                  cx={cx}
                  cy={cy}
                  r={3.4}
                  stroke="currentColor"
                  strokeOpacity={0.3}
                  strokeWidth={1.6}
                />
              ))}
              {PINS.map((x) => (
                <line
                  key={x}
                  x1={x}
                  x2={x}
                  y1={62}
                  y2={72}
                  stroke="currentColor"
                  strokeOpacity={0.35}
                  strokeWidth={3}
                  strokeLinecap="round"
                />
              ))}
            </g>
          ))}

          {/* Current pulses racing along the traces into the chip */}
          {!reduce && (
            <g filter={`url(#circuit-glow-${uid})`}>
              {SIDES.map((deg, side) => (
                <g key={`pulses-${deg}`} transform={`rotate(${deg} 100 100)`}>
                  {TRACES.map((d, t) => {
                    const index = side * TRACES.length + t;
                    return (
                      <motion.path
                        key={d}
                        d={d}
                        stroke={color}
                        strokeWidth={3.2}
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        pathLength={1}
                        // A short bright dash, followed by a gap longer than the path
                        strokeDasharray="0.18 1.2"
                        initial={{ strokeDashoffset: 0.18 }}
                        animate={{ strokeDashoffset: [0.18, -1] }}
                        transition={{
                          duration: speed,
                          ease: "linear",
                          repeat: Infinity,
                          delay: FIRE_ORDER[index] * slot,
                        }}
                      />
                    );
                  })}
                </g>
              ))}
            </g>
          )}

          {/* Chip halo — breathes as current flows in */}
          <motion.rect
            x={76}
            y={76}
            width={48}
            height={48}
            rx={10}
            fill={color}
            filter={`url(#circuit-halo-${uid})`}
            initial={{ opacity: 0.25 }}
            animate={
              reduce ? { opacity: 0.25 } : { opacity: [0.15, 0.4, 0.15] }
            }
            transition={
              reduce
                ? { duration: 0 }
                : { duration: speed, ease: "easeInOut", repeat: Infinity }
            }
          />

          {/* Chip body */}
          <rect
            x={72}
            y={72}
            width={56}
            height={56}
            rx={9}
            fill="currentColor"
            fillOpacity={0.05}
            stroke="currentColor"
            strokeOpacity={0.3}
            strokeWidth={1.6}
          />
          <rect
            x={82}
            y={82}
            width={36}
            height={36}
            rx={6}
            stroke="currentColor"
            strokeOpacity={0.12}
            strokeWidth={1.2}
          />
          {/* Orientation dot, like pin 1 on a real chip */}
          <circle
            cx={79}
            cy={79}
            r={1.6}
            fill="currentColor"
            fillOpacity={0.35}
          />

          {/* Lightning bolt — flickers like a live connection */}
          <motion.path
            d={BOLT}
            fill={color}
            filter={`url(#circuit-glow-${uid})`}
            initial={{ opacity: 1 }}
            animate={
              reduce ? { opacity: 1 } : { opacity: [0.6, 1, 0.75, 1, 0.6] }
            }
            transition={
              reduce
                ? { duration: 0 }
                : {
                    duration: speed,
                    times: [0, 0.1, 0.18, 0.3, 1],
                    ease: "easeInOut",
                    repeat: Infinity,
                  }
            }
          />
        </svg>
      </div>

      {label && (
        <motion.span
          aria-hidden="true"
          className="text-sm font-medium tracking-[0.2em] text-neutral-400 uppercase"
          animate={reduce ? {} : { opacity: [0.4, 1, 0.4] }}
          transition={{ duration: 1.6, repeat: Infinity, ease: "easeInOut" }}
        >
          {label}
        </motion.span>
      )}
    </div>
  );
}
// Component Source: https://codekk.dev
DesktopSandpackTailwind v4

Component details

Overview, install command, dependencies, and project links.

A circuit board loader for React built with TypeScript, Framer Motion, SVG, and Tailwind CSS, designed for electronics stores, tech brands, EV charging, and smart home websites. A microchip sits at the center of a small circuit board, and bursts of electric current race along copper traces from all four sides into it. When the current arrives, a lightning bolt on the chip flickers like a live connection, and a soft halo around the chip breathes with the flow of power. The board has realistic detail: 12 traces with angled bends, ring-shaped vias where each trace starts, four pins on every side of the chip, an inner die outline, and a pin-1 orientation dot in the corner. The traces for one side are defined once and rotated for the other three, so the board stays perfectly symmetrical. Each pulse is a short glowing dash animated along its trace with pathLength and strokeDashoffset, and the gap after it is longer than the trace, so each trace carries exactly one pulse at a time. The 12 pulses fire in a shuffled order but stay evenly spaced in time, so the board always looks active without everything flashing at once. The bolt flickers on an irregular rhythm for a more electric feel. A single color prop sets the pulses, bolt, and halo, so the loader matches any brand color. The board lines use currentColor at low opacity, so they adapt to light or dark themes automatically. SVG filters add a tight glow to the pulses and a wide halo behind the chip, with IDs from React's useId, so multiple loaders can share a page without conflicts. The wrapper uses role="status" with an accessible label, and the visuals are hidden from screen readers. When prefers-reduced-motion is enabled, the pulses are hidden and the chip and bolt stay steadily lit. It works in Vite, Create React App, and Next.js.

Added Sep 27, 2026

Discussion

Feedback, implementation tips, and usage notes.

0 threads

Comments

Sign in to join the conversation