Logo Wall
Installation
npx shadcn@latest add https://uibrik.com/r/logo-wall.jsonAdd the cn helper
Imports
cnfrom@/lib/utils, whichshadcn initcreates. Add it by hand without shadcn.Copy the source into your project
components/uibrik/logo-wall/logo-wall.tsx"use client"; import type { CSSProperties, ReactNode } from "react"; import { cn } from "@/lib/utils"; // Logo wall. A grid of customer logos at rest in one quiet grey, split by // sharp hairlines. Pointing at a logo lights only that one: it takes its own // colour with a soft glow, and its cell's edge fades in as a gradient of the // same colour all the way round, deepest at the top. Plain CSS, so it // costs nothing while the pointer moves; keyboard focus on a linked logo // lights it the same way. export type Logo = { name: string; /** Drawn mark beside the name. Uses currentColor. */ mark?: ReactNode; /** Image logo instead of a mark and name. Shown in grey at rest. */ src?: string; /** Brand colour, shown while lit. */ color?: string; href?: string; }; const mark = (d: ReactNode) => ( <svg viewBox="0 0 24 24" className="size-5 shrink-0" fill="currentColor" aria-hidden> {d} </svg> ); const LOGOS: Logo[] = [ { name: "Halcyon", color: "#5a2ee8", mark: mark(<><circle cx="12" cy="12" r="4" /><path d="M12 3a9 9 0 1 1 0 18a9 9 0 0 1 0-18Zm0 2a7 7 0 1 0 0 14a7 7 0 0 0 0-14Z" /></>) }, { name: "Northwind", color: "#0ea5e9", mark: mark(<path d="M12 2l9 18h-6l-3-6-3 6H3z" />) }, { name: "Lumen", color: "#f59e0b", mark: mark(<><circle cx="12" cy="12" r="5" /><path d="M11 1h2v4h-2zM11 19h2v4h-2zM1 11h4v2H1zM19 11h4v2h-4z" /></>) }, { name: "Arcadia", color: "#16a34a", mark: mark(<path d="M5 21C5 10 11 4 21 3c-1 10-7 16-16 18Zm2-2c6-2 10-7 11-13-6 1-10 5-11 13Z" />) }, { name: "Kestrel", color: "#e11d48", mark: mark(<path d="M2 14 12 4l10 10h-5l-5-5-5 5z M7 20l5-5 5 5z" />) }, { name: "Monolith", color: "#71717a", mark: mark(<path d="M4 3h7v18H4zM13 8h7v13h-7z" />) }, { name: "Tidal", color: "#0891b2", mark: mark(<path d="M2 9c3-3 5-3 8 0s5 3 8 0l4-1v3c-3 3-6 3-8 0s-5-3-8 0L2 12zM2 15c3-3 5-3 8 0s5 3 8 0l4-1v3c-3 3-6 3-8 0s-5-3-8 0l-4 1z" />) }, { name: "Quanta", color: "#7c3aed", mark: mark(<path d="M12 2 21 7v10l-9 5-9-5V7zm0 4-5 3v6l5 3 5-3V9z" />) }, ]; export type LogoWallProps = { /** Muted line above the grid. Empty hides it. */ title?: string; logos?: Logo[]; /** Columns on wide screens; two on phones. */ columns?: number; /** Light a logo in its brand colour. Off, it lights in the text colour. */ colorful?: boolean; /** Strength of the glow around a lit logo, px of blur. 0 turns it off. */ glow?: number; className?: string; }; // The gradient edge: a 1px ring cut out of a gradient fill with two masks. const RING: CSSProperties = { padding: 1, // All four sides, shading from the full colour at the top to a softer tint at the bottom. background: "linear-gradient(to bottom, var(--lit), color-mix(in oklab, var(--lit) 30%, transparent))", mask: "linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)", WebkitMask: "linear-gradient(#000 0 0) content-box xor, linear-gradient(#000 0 0)", }; export function LogoWall({ title = "Trusted by teams at", logos = LOGOS, columns = 4, colorful = true, glow = 14, className, }: LogoWallProps) { return ( <section aria-label={title || "Customers"} className={cn("w-full max-w-160", className)}> {title && <p className="mb-5 text-center text-sm text-muted-foreground">{title}</p>} {/* Sharp hairlines: the grid draws the top and left edge, each cell its right and bottom. */} <ul className="grid grid-cols-2 border-t border-l border-foreground/[0.08] sm:grid-cols-[repeat(var(--cols),minmax(0,1fr))]" style={{ "--cols": columns } as CSSProperties} > {logos.map((logo) => { const lit = (colorful && logo.color) || "var(--foreground)"; const content = logo.src ? ( // eslint-disable-next-line @next/next/no-img-element <img src={logo.src} alt={logo.name} draggable={false} className="h-6 w-auto max-w-[70%] object-contain opacity-45 grayscale transition-[opacity,filter] duration-300 group-hover:opacity-100 group-hover:grayscale-0 group-focus-visible:opacity-100 group-focus-visible:grayscale-0" /> ) : ( <span className="flex items-center gap-2 text-[15px] font-medium tracking-tight"> {logo.mark} {logo.name} </span> ); const cell = cn( "group relative flex h-24 items-center justify-center border-r border-b border-foreground/[0.08] text-foreground/35", // Lifted while lit, so its edge draws over the neighbours' hairlines. "cursor-pointer outline-none transition-colors duration-500 ease-out hover:text-(--lit) hover:duration-200 focus-visible:text-(--lit) focus-visible:duration-200", ); const body = ( <> {/* Edge: stacked over the cell's and its neighbours' hairlines, so it covers the whole square while lit and stays on top as it fades out. Quick in, slower out. */} <span aria-hidden className="pointer-events-none absolute -inset-px z-10 opacity-0 transition-opacity duration-500 ease-out group-hover:opacity-100 group-hover:duration-200 group-focus-visible:opacity-100 group-focus-visible:duration-200" style={RING} /> <span className="[filter:drop-shadow(0_0_0_transparent)] transition-[filter] duration-300 group-hover:[filter:drop-shadow(0_0_var(--glow)_color-mix(in_oklab,var(--lit)_55%,transparent))] group-focus-visible:[filter:drop-shadow(0_0_var(--glow)_color-mix(in_oklab,var(--lit)_55%,transparent))]" > {content} </span> </> ); const style = { "--lit": lit, "--glow": `${glow}px` } as CSSProperties; return ( <li key={logo.name} className="contents"> {logo.href ? ( <a href={logo.href} target="_blank" rel="noreferrer" className={cell} style={style}> {body} </a> ) : ( <div className={cell} style={style}> {body} </div> )} </li> ); })} </ul> </section> ); }
Adds the files to
components/uibrik/logo-wall. With the @uibrik namespace set up: npx shadcn@latest add @uibrik/logo-wall.Props
LogoWall- title
- Type string
- "Trusted by teams at"
- Muted line above the grid. Empty hides it.
- columns
- Type number
- 4
- Columns on wide screens; phones always get two.
- glow
- Type number
- 14
- Blur of the glow around a lit logo. 0 turns it off.
- colorful
- Type boolean
- true
- Light a logo in its brand colour. Off, it lights in the text colour.
- logos
- Type { name: string; mark?: ReactNode; src?: string; color?: string; href?: string }[]
- 8 sample logos
- Logos in order. A drawn mark with the name, or an image via src. With an href the cell is a link.
- className
- Type string
- –
- Classes for the wrapper.