COMPONENT / logo-wall
Logo Wall_
Hairline grid of logo cells after a label cell, with signal hover on linked logos. One row from lg. Requires @cybr/tokens.
Install
1. Map the
@cybrnamespace, once per project. Add this inside yourcomponents.json, or into its existing"registries"object. Nocomponents.jsonyet? Runnpx shadcn@latest initfirst to create one."registries": { "@cybr": "https://cybr-ui.com/r/{name}.json" }2. Add components:
$ npx shadcn@latest add @cybr/logo-wall
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
registry/ui/logo-wall.tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
// Hairline logo grid: a label cell, then equal logo cells. 2 columns on small
// screens, 3 from sm, and one row from lg. Gaps over a --line parent become the
// borders.
export function LogoWall({ label, className, children, ...props }: ComponentProps<"div"> & { label?: ReactNode }) {
return (
<div
className={cn(
"grid grid-cols-2 gap-(--border-base) border-b-(length:--border-base) border-line bg-line font-mono sm:grid-cols-3 lg:flex",
className,
)}
{...props}
>
{label ? (
<div className="col-span-full flex items-center bg-bg px-8 py-4 text-xs tracking-label text-label lg:w-55 lg:shrink-0">
{label}
</div>
) : null}
{children}
</div>
);
}
// One logo cell. Renders a link when `href` is set, otherwise a plain cell.
// Logo images and their alt text are the caller's.
export function LogoWallItem({ href, className, children, ...props }: ComponentProps<"a">) {
const cell = cn(
"flex w-full items-center justify-center border-(length:--border-base) border-transparent text-xs font-bold tracking-[0.16em] text-label no-underline",
href &&
"transition-colors duration-(--duration-snap) ease-step-2 hover:border-signal hover:text-signal focus-visible:outline-(length:--focus-width) focus-visible:outline-offset-[-3px] focus-visible:outline-signal motion-reduce:transition-none",
className,
);
return (
<div className="flex min-h-18 bg-bg lg:h-24 lg:min-w-0 lg:flex-1">
{href ? (
<a href={href} className={cell} {...props}>
{children}
</a>
) : (
<div className={cell}>{children}</div>
)}
</div>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | ReactNode | — | Text for the leading label cell. Omitted when unset. |
| children | ReactNode | — | LogoWallItem cells. Any number is allowed. |
| ...props | ComponentProps<"div"> | — | Every other native <div> attribute is forwarded; className is merged last with cn(). |
| Item href | string | — | Makes the cell a link with a signal hover. Without it, the cell is plain. |
| Item children | ReactNode | — | The logo: text, an <img> with alt text, or an inline SVG with a title. |
| Item ...props | ComponentProps<"a"> | — | Forwarded to the <a> when href is set; className is merged last with cn() on the inner cell. |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import { LogoWall, LogoWallItem } from "@/components/ui/logo-wall";
export function Clients() {
return (
<LogoWall label="TRUSTED BY">
<LogoWallItem href="https://example.com">
<img src="/logos/example.svg" alt="Example Corp" className="h-6" />
</LogoWallItem>
</LogoWall>
);
}
States
- Idle:
--labeltext in a cell with a transparent 2px border. - Hover: linked logos turn
--signal, text and border, in two hard steps over 90ms. - Focus-visible: a 3px solid
--signaloutline inset inside the cell. - Reduced motion: the hover change snaps with no transition.
- Layout: 2 columns below
smwith the label across the top, 3 fromsm, and one row fromlgwith a 220px label cell.
Accessibility
- Each linked logo is a native
<a>. Its accessible name comes from its content, so give logo imagesalttext naming the company. - Unlinked cells are plain
<div>s with no hover. - The label is
--labelon--bg(7.43:1). Hovered text is--signalon--bg(4.64:1). If you override colours withclassName, keep text contrast at 4.5:1 or above.