Orbiting Icons

Integration icons circling a central logo on two orbits.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/orbiting-icons.tsx

    components/ui/orbiting-icons.tsx
    "use client";
    
    import { motion } from "motion/react";
    import { Cloud, Database, Figma, Github, Mail, MessageSquare, Slack, Zap, type LucideIcon } from "lucide-react";
    import type { ReactNode } from "react";
    
    type Orbit = { radius: number; duration: number; icons: LucideIcon[]; reverse?: boolean };
    
    const defaultOrbits: Orbit[] = [
      { radius: 80, duration: 18, icons: [Slack, Github, Figma] },
      { radius: 150, duration: 28, icons: [Mail, Database, Cloud, MessageSquare, Zap], reverse: true },
    ];
    
    const round = (n: number) => Math.round(n * 100) / 100;
    
    export function OrbitingIcons({ orbits = defaultOrbits, center }: { orbits?: Orbit[]; center?: ReactNode }) {
      const size = Math.max(...orbits.map((o) => o.radius)) * 2 + 56;
    
      return (
        <div className="relative grid place-items-center" style={{ width: size, height: size }}>
          {orbits.map((orbit, oi) => {
            const spin = { duration: orbit.duration, repeat: Infinity, ease: "linear" as const };
            return (
              <div
                key={oi}
                className="absolute rounded-full border border-dashed border-zinc-200"
                style={{ width: orbit.radius * 2, height: orbit.radius * 2 }}
              >
                <motion.div className="absolute inset-0" animate={{ rotate: orbit.reverse ? -360 : 360 }} transition={spin}>
                  {orbit.icons.map((Icon, i) => {
                    const angle = (i / orbit.icons.length) * Math.PI * 2;
                    return (
                      <motion.div
                        key={i}
                        className="absolute grid size-11 place-items-center rounded-xl border border-zinc-200 bg-white shadow-sm"
                        style={{
                          left: round(orbit.radius + orbit.radius * Math.cos(angle) - 22),
                          top: round(orbit.radius + orbit.radius * Math.sin(angle) - 22),
                        }}
                        animate={{ rotate: orbit.reverse ? 360 : -360 }}
                        transition={spin}
                      >
                        <Icon className="size-5 text-zinc-700" />
                      </motion.div>
                    );
                  })}
                </motion.div>
              </div>
            );
          })}
          <div className="relative z-10">{center}</div>
        </div>
      );
    }
    
    export default function Demo() {
      return (
        <OrbitingIcons
          center={
            <motion.div
              className="grid size-16 place-items-center rounded-2xl bg-zinc-900 text-white shadow-xl"
              animate={{ boxShadow: ["0 0 0 0 rgba(37,99,235,0.4)", "0 0 0 16px rgba(37,99,235,0)"] }}
              transition={{ duration: 2, repeat: Infinity }}
            >
              <Zap className="size-7 fill-white" />
            </motion.div>
          }
        />
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.