Logo Cloud Hover
A logo wall that dims and blurs on hover to reveal a single call-to-action, installed as a wired Payload block.
import type { Block } from 'payload'import { logoCloudFields } from '@/blocks/shared/logoCloudFields'import { linkGroup } from '@/fields/linkGroup'export const LogoCloudHover: Block = { slug: 'logoCloudHover', interfaceName: 'LogoCloudHoverBlock', fields: [ // Shared logo-cloud core (heading + logos). Edit the shared shape in // @/blocks/shared/logoCloudFields to update every logo-cloud variant. ...logoCloudFields, // Variant-specific: a single CTA revealed on hover over the logo wall. linkGroup({ overrides: { admin: { initCollapsed: true, }, maxRows: 1, }, }), ], labels: { plural: 'Logo Cloud Hover Blocks', singular: 'Logo Cloud Hover', },}import React from 'react'import type { LogoCloudHoverBlock as LogoCloudHoverBlockData } from '@/payload-types'import { CMSLink } from '@/components/Link'import { Media } from '@/components/Media'import { cn } from '@/utilities/ui'type Props = LogoCloudHoverBlockData & { id?: string className?: string disableInnerContainer?: boolean}export const LogoCloudHoverBlock: React.FC<Props> = ({ className, disableInnerContainer, heading, id, links, logos,}) => { return ( <section className={cn('container', className)} id={id ? `block-${id}` : undefined}> <div className="overflow-hidden rounded-frame border border-border/70 bg-card/35 px-6 py-10 sm:px-8 lg:px-12 lg:py-14"> <div className={cn('flex flex-col gap-12', { 'mx-auto max-w-5xl': !disableInnerContainer, })} > <p className="text-center text-sm font-medium text-muted-foreground">{heading}</p> <div className="group relative"> {links && links.length > 0 ? ( <div className="pointer-events-none absolute inset-0 z-10 flex scale-95 items-center justify-center opacity-0 transition duration-500 group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100"> {links.map(({ link }, index) => ( <CMSLink appearance={link.appearance === 'outline' ? 'outline' : 'default'} key={index} {...link} /> ))} </div> ) : null} {logos && logos.length > 0 ? ( <div className="grid grid-cols-3 gap-x-12 gap-y-8 transition-all duration-500 group-hover:opacity-40 group-hover:blur-sm group-focus-within:opacity-40 group-focus-within:blur-sm sm:gap-x-16 sm:gap-y-12 md:grid-cols-4"> {logos.map((item, index) => { const logo = ( <Media resource={item.logo} imgClassName="mx-auto h-6 w-auto object-contain" /> ) return ( <div className="flex items-center justify-center" key={item.id ?? `${item.name}-${index}`} > {item.href ? ( <a aria-label={item.name} href={item.href}> {logo} </a> ) : ( logo )} </div> ) })} </div> ) : null} </div> </div> </div> </section> )}import type { Field } from 'payload'/** * Shared field core for the Logo Cloud kit family. * * Every logo-cloud variant (logo-cloud-grid, logo-cloud-hover, * logo-cloud-marquee, logo-cloud-inline, logo-cloud-inline-wrap, …) spreads * these fields first and then appends its own variant-specific shape (for * example the hover variant adds a CTA link group). Editing the shared * heading/logos shape here updates every installed logo-cloud block at once, * so the family never drifts field-by-field across a repo. * * Each logo is an editable Media upload plus an accessible name and an * optional link, so editors manage the wall of logos from the admin instead * of shipping hardcoded brand SVGs. * * Installed once per repo at `src/blocks/shared/logoCloudFields.ts`; re-running * `payload-components add logo-cloud-*` never overwrites a copy you have already edited. */export const logoCloudFields: Field[] = [ { name: 'heading', type: 'text', required: true, }, { name: 'logos', type: 'array', required: true, minRows: 2, maxRows: 12, admin: { initCollapsed: true, }, fields: [ { name: 'logo', type: 'upload', relationTo: 'media', required: true, }, { name: 'name', type: 'text', required: true, }, { name: 'href', type: 'text', }, ], },]Installation
npx payload-components add logo-cloud-hoverCopy the files straight from the registry, then wire the Payload fragments by hand:
pnpm dlx shadcn@latest add https://www.payload-components.xyz/r/logo-cloud-hover.jsonWhat it installs
Copies 3 source files into your project:
src/blocks/shared/logoCloudFields.tssharedsrc/blocks/LogoCloudHover/config.tssrc/blocks/LogoCloudHover/Component.tsx
…and makes 4 edits to wire the block into your project:
| Registers the block | src/collections/Pages/index.ts |
| Maps the renderer | src/blocks/RenderBlocks.tsx |
| Regenerates types | src/payload-types.ts |
| Regenerates the admin import map | src/app/(payload)/admin/importMap.js |
logoCloudFields.ts is the shared field core for this family — every variant composes it. Editing it updates each installed block at once, and re-running an install never overwrites a copy you have changed.Re-running the install converges: it detects existing wiring, skips it, and records install state in .payload-components/state.json.
Content model
heading and logos come from the shared logoCloudFields base; links is specific to this
variant — a single CTA revealed when the logo wall is hovered.
Prop
Type
Each item in logos carries:
Prop
Type
Usage
LogoCloudHover block to its layout.RenderBlocks on the frontend, fully typed — no extra wiring.Requirements
- Target
- payload-website-starter
- Payload
- v3
- Next.js
- 15 / 16
- shadcn UI
- none
Your project must already expose components.json, src/payload.config.ts, src/blocks/RenderBlocks.tsx, src/collections/Pages/index.ts — the surfaces payload-components add patches. The CLI verifies this against the support matrix before touching anything.
In this family
logo-cloud-gridlogo-cloud-hovercurrentlogo-cloud-marqueelogo-cloud-inlinelogo-cloud-inline-wrap