Blog//7 min read
Agency hero section in React with a client marquee
Build an agency hero section in React: oversized type that reveals line by line, an availability CTA, a client wordmark marquee and scroll depth tracking.
MSMarco Schulz, founder of Facade UI
An agency hero has a harder job than a plumber hero. The plumber's visitor wants a phone number. The agency's visitor wants to know, in about four seconds, whether this studio is good enough and whether it is the kind of good they are looking for. You cannot prove taste with a paragraph. You prove it with the type, the spacing and one row of names people recognize. The Hero, agency block in Facade is built around that idea: oversized display type, one sentence, one button, a line about availability, and a wordmark marquee underneath. Here is how each part works and why.
Oversized type
The headline is the hero. At its largest breakpoint it renders at 96 pixels with a line height of 1 and letter spacing pulled in to about minus 0.045em. That tight tracking is what makes big sans serif type look designed instead of just big; browser defaults at that size look loose. The headline is capped at 14 characters per line with max-w-[14ch] and uses text-balance, so the lines break evenly instead of leaving one word stranded.
The headline is an array of lines, not a string, and each line animates in on its own: it slides up from behind an overflow-hidden wrapper over 0.9 seconds, staggered by a tenth of a second per line, on a strong ease-out curve. With reduced motion it fades instead. Two lines is the sweet spot. Three is fine. Four means the sentence is too long for a hero.
Wrap a word in asterisks and it renders in the muted foreground color, which gives you one point of emphasis without a second typeface or a brand colored word fighting the button for attention.
import { HeroAgency } from "@/components/facade/hero-agency";
<HeroAgency
eyebrow="Design and development studio, Portland"
headline={["Websites that earn", "their *keep*."]}
description="We design and build sites for companies that sell something real. Twelve people, no account managers, every project led by a partner."
primaryCta={{ label: "Start a project", href: "/contact" }}
secondaryCta={{ label: "See the work", href: "/work" }}
availability="Two project openings for Q1"
clientsLabel="Trusted by"
clients={["Northwind Outdoor", "Harbor Freight Co.", "Lumen Health", "Redline Auto", "Alder & Vine", "Peak Realty"]}
/>The background
The hero sits on the plain page background, relative isolate overflow-hidden, with no image. That is a decision, not an omission. An agency hero with a photo competes with the work shown further down, and the work should win. The motion lives in the type reveal and the marquee; the background stays still. If you want texture, add a grid or gradient layer behind the content in your own copy of the block, but try it without first.
The availability CTA
Under the two buttons sits one line: availability. "Two project openings for Q1" does more than "Get in touch" because it answers the question the visitor was about to ask and implies demand without claiming it. The rule is that it has to be true. Update it when a slot fills. When the studio is fully booked, say so: "Booking for March" is a fine availability line and it filters out the people who needed something yesterday.
The primary button is a brand colored pill, the secondary a muted one. Both are real anchors with visible focus rings. No ghost buttons, no icon only buttons.
The wordmark marquee
Client logos are the proof, and the usual logo row has two problems: real logos arrive in six different styles and weights, and a static row of six leaves you choosing which six. The hero solves both with a marquee of wordmarks: each client name is set in one of two type treatments, uppercase with wide tracking or uppercase semibold with tighter tracking, alternating. It reads like a row of logos without a single SVG, and it stays consistent across clients.
The marquee is built on the animate-marquee keyframe from the Facade tokens. The list of clients is rendered twice inside a w-max track, the track translates by exactly one copy's width and loops, and the second copy is marked aria-hidden so a screen reader reads each client once. The edges fade with a mask-image gradient so names appear and disappear instead of being clipped. The duration is 44 seconds for a full loop, which is slow enough to read a name as it passes, and it pauses on hover.
If you want the same marquee elsewhere on the page, with real logos, the free Marquee primitive is the same mechanism exposed as a component: speed, direction, pauseOnHover, fade, vertical and a repeat prop for when your content is narrower than the container.
import { Marquee } from "@/components/facade/marquee";
<Marquee speed={40} fade pauseOnHover repeat={2} gap="4rem">
<img src="/logos/northwind.svg" alt="Northwind Outdoor" className="h-6" />
<img src="/logos/lumen.svg" alt="Lumen Health" className="h-6" />
<img src="/logos/redline.svg" alt="Redline Auto" className="h-6" />
</Marquee>And when the client list deserves a section of its own, the free Logo cloud block renders the names as a grid where hovering a client lifts the name and reveals a one line outcome, like "+212% booked jobs". Pass variant="marquee" and it becomes two scrolling rows instead. The outcomes are the part agencies skip and should not: a name says you worked with them, a number says what happened.
import { LogoCloud } from "@/components/facade/logo-cloud";
<LogoCloud
eyebrow="Selected clients"
title="Companies we have worked with since 2019"
variant="grid"
logos={[
{ name: "Northwind Outdoor", detail: "Rebrand and e-commerce", href: "/work/northwind" },
{ name: "Lumen Health", detail: "Patient portal redesign", href: "/work/lumen" },
{ name: "Peak Realty", detail: "41 percent more listing inquiries", href: "/work/peak" },
{ name: "Alder & Vine", detail: "Brand, packaging and site", href: "/work/alder-vine" },
]}
/>Install the three free items in one command:
npx shadcn@latest add https://facadeui.com/r/hero-agency.json https://facadeui.com/r/logo-cloud.json https://facadeui.com/r/marquee.jsonMeasuring scroll depth
A hero that looks good and does nothing is a poster. The number I watch on agency sites is how many visitors get past the hero to the work, and how many click the primary button. Both are easy to measure without a heavy analytics setup.
For scroll depth, put an IntersectionObserver on the first element after the hero and fire one event when it enters the viewport:
React.useEffect(() => {
const el = document.getElementById("work");
if (!el) return;
const io = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
track("hero_passed");
io.disconnect();
}
}, { threshold: 0.2 });
io.observe(el);
return () => io.disconnect();
}, []);Wire track to whatever you use: Vercel Analytics custom events, Plausible goals, GA4 events. For the button, an onClick on the primary CTA with the same track call. Compare the two numbers over a month. If many visitors scroll past the hero but few click, the headline is doing its job and the offer is not. If few scroll past, the headline is the problem.
On the marketing agency page the hero is followed by the logo cloud, the features bento and the process steps, so the first thing a visitor hits after the type is the proof. That order is deliberate: claim, then evidence, then how it works.
FAQ
Can I use a real logo instead of a wordmark in the hero marquee?
The hero's clients prop takes strings and renders them as styled text on purpose, so a mixed bag of client logos does not break the composition. For real logos, use the Marquee primitive or the Logo cloud with its logo slot, which accepts any React node.
How do I keep the headline from breaking badly on phones?
The block already scales from 44 pixels on small screens to 96 on large and balances line breaks. Keep each line short and let the array decide where lines end. If a word still orphans, make the line shorter rather than fighting it with manual breaks.
Does the type reveal hurt Largest Contentful Paint?
The headline is in the server rendered HTML, so the text exists at first paint and is only translated into place after hydration. Google's documented threshold for a good LCP is 2.5 seconds, and a text hero with no image clears that comfortably on normal hosting. Visitors with reduced motion enabled see the headline instantly.
Components in this article
- Hero, agencyFreeStudio hero with oversized centered display type, one ultramarine CTA and a quiet client wordmark marquee.
- Logo cloudFreeTrusted by wordmark grid with hover reveal of client outcomes and an optional two row marquee mode.
- MarqueeFreeSeamless infinite marquee for logos or testimonials with faded edges, direction, speed and vertical mode.