docsStacked Logos

Stacked Logos

Multiple logo groups that animate in/out while stacked on top of each other. Features a Vercel-style grid with mouse-following glow effect on borders.

Loading Preview...

Install using CLI

npx shadcn@latest add "https://www.vengenceui.com/r/stacked-logos.json"

Install Manually

1

Add util file

lib/utils.ts

import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
2

Add styles to global CSS

Add the following styles to your app/globals.css file

/* StackedLogos - CSS Variables & Animations */
.stacked-logos {
--duration: 30;
--items: 4;
--lists: 4;
--stagger: 0;
--logo-width: 200px;
--mouse-x: 0px;
--mouse-y: 0px;
}
.stacked-logos:hover .stacked-logos__glow {
opacity: 1;
}
.stacked-logos:hover .stacked-logos__border-glow {
opacity: 1;
}
.stacked-logos__cell {
--base-delay: calc(sin((var(--index) / var(--lists)) * 45deg) * var(--stagger));
}
.stacked-logos__logo {
animation-name: stacked-logos-appear;
animation-duration: calc(var(--duration) * 1s);
animation-fill-mode: both;
animation-iteration-count: infinite;
animation-delay: calc(
(var(--duration) / var(--items)) * (var(--items) - var(--i)) * -1s +
(var(--base-delay, 0) * 1s)
);
}
@keyframes stacked-logos-appear {
0%, 100% { opacity: 0; filter: blur(4px); }
5%, 20% { opacity: 1; filter: blur(0); }
25%, 100% { opacity: 0; filter: blur(4px); }
}
@media (prefers-reduced-motion: reduce) {
.stacked-logos__logo {
animation: none;
opacity: 1;
filter: none;
}
.stacked-logos__item:not(:first-child) {
display: none;
}
}
3

Copy the source code

Copy the code below and paste it into components/ui/stacked-logos.tsx

"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
export interface StackedLogosProps {
logoGroups: React.ReactNode[][];
duration?: number;
stagger?: number;
logoWidth?: string;
className?: string;
}
export const StackedLogos = ({
logoGroups,
duration = 30,
stagger = 0,
logoWidth = "200px",
className,
}: StackedLogosProps) => {
const itemCount = logoGroups[0]?.length || 0;
const columns = logoGroups.length;
const containerRef = React.useRef<HTMLDivElement>(null);
const gridRef = React.useRef<HTMLDivElement>(null);
const handleMouseMove = React.useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current || !gridRef.current) return;
const rect = gridRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
containerRef.current.style.setProperty('--mouse-x', `${x}px`);
containerRef.current.style.setProperty('--mouse-y', `${y}px`);
}, []);
return (
<div
ref={containerRef}
className={cn("stacked-logos relative w-full", className)}
style={{
"--duration": duration,
"--items": itemCount,
"--lists": columns,
"--stagger": stagger,
"--logo-width": logoWidth,
} as React.CSSProperties}
onMouseMove={handleMouseMove}
>
<div ref={gridRef} className="grid relative" style={{ gridTemplateColumns: `repeat(${columns}, ${logoWidth})` }}>
{/* Background glow */}
<div className="stacked-logos__glow pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 z-10"
style={{ background: 'radial-gradient(500px circle at var(--mouse-x, 0) var(--mouse-y, 0), rgba(251,191,36,0.1), transparent 70%)' }} />
{/* Border glow */}
<div className="stacked-logos__border-glow pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 z-20"
style={{
background: 'radial-gradient(600px circle at var(--mouse-x, 0) var(--mouse-y, 0), rgba(251,191,36,1), transparent 40%)',
maskImage: `repeating-linear-gradient(to right, transparent, transparent calc(${logoWidth} - 1px), black calc(${logoWidth} - 1px), black ${logoWidth}), linear-gradient(to bottom, black 0, black 1px, transparent 1px, transparent calc(100% - 1px), black calc(100% - 1px), black 100%)`,
WebkitMaskImage: `repeating-linear-gradient(to right, transparent, transparent calc(${logoWidth} - 1px), black calc(${logoWidth} - 1px), black ${logoWidth}), linear-gradient(to bottom, black 0, black 1px, transparent 1px, transparent calc(100% - 1px), black calc(100% - 1px), black 100%)`,
maskComposite: 'add',
}} />
{/* Left edge glow */}
<div className="stacked-logos__border-glow pointer-events-none absolute top-0 bottom-0 left-0 w-px opacity-0 transition-opacity duration-300 z-20"
style={{ background: 'radial-gradient(600px circle at var(--mouse-x, 0) var(--mouse-y, 0), rgba(251,191,36,1), transparent 40%)' }} />
{logoGroups.map((logos, groupIndex) => (
<div key={groupIndex} className="stacked-logos__cell relative grid"
style={{ "--index": groupIndex, gridTemplate: "1fr / 1fr" } as React.CSSProperties}>
{/* Border lines */}
<div className="absolute top-0 bottom-0 right-0 w-px bg-zinc-200 dark:bg-zinc-800" />
<div className="absolute left-0 right-0 bottom-0 h-px bg-zinc-200 dark:bg-zinc-800" />
<div className="absolute left-0 right-0 top-0 h-px bg-zinc-200 dark:bg-zinc-800" />
{groupIndex === 0 && <div className="absolute top-0 bottom-0 left-0 w-px bg-zinc-200 dark:bg-zinc-800" />}
{logos.map((logo, logoIndex) => (
<div key={logoIndex} className="stacked-logos__item col-start-1 row-start-1 grid place-items-center py-16 px-8"
style={{ "--i": logoIndex } as React.CSSProperties}>
<div className="stacked-logos__logo w-full h-8 flex items-center justify-center [&>svg]:h-full [&>svg]:w-auto [&>svg]:fill-zinc-700 dark:[&>svg]:fill-zinc-300">
{logo}
</div>
</div>
))}
</div>
))}
</div>
</div>
);
};
StackedLogos.displayName = "StackedLogos";
export default StackedLogos;

Usage

1import { StackedLogos } from "@/components/ui/stacked-logos"
2
3const logos1 = [<Logo1 />, <Logo2 />, <Logo3 />, <Logo4 />]
4const logos2 = [<Logo5 />, <Logo6 />, <Logo7 />, <Logo8 />]
5
6export function PartnersSection() {
7return (
8 <StackedLogos
9 logoGroups={[logos1, logos2, logos3, logos4]}
10 duration={20}
11 stagger={2}
12 logoWidth="200px"
13 />
14)
15}

Fast Animation

Loading Preview...

Slow Animation

Loading Preview...

Two Groups Only

Loading Preview...

Custom Width

Loading Preview...

Props

Prop NameTypeDefaultDescription
logoGroupsReact.ReactNode[][]-Array of logo groups. Each group is an array of logo elements.
durationnumber30Full animation cycle duration in seconds.
staggernumber0Stagger factor for timing offset between groups.
logoWidthstring"200px"Width of each logo container.
classNamestring-Additional classes.