# Hero, agency

> Studio hero with oversized centered display type, one ultramarine CTA and a quiet client wordmark marquee.

A shadcn/ui block from the [@facadeui](https://facadeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/facadeui/hero-agency).

- Page, with a live preview: https://shadcn.exchange/r/facadeui/hero-agency
- Screenshot: https://shadcn.exchange/shot/facadeui/hero-agency/light.png
- Registry item: https://facadeui.com/r/hero-agency.json
- Categories: hero
- Listed: 2026-11-05, source last changed 2026-11-05

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/facadeui/hero-agency.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `motion`
- Registry items: `utils`

## Files

### `registry/items/hero-agency/hero-agency.tsx`

```tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "motion/react";
import { ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";

export interface HeroAgencyProps {
  eyebrow?: string;
  /** Headline lines. Wrap a word in *asterisks* to render it in the secondary color. */
  headline?: string[];
  description?: string;
  primaryCta?: { label: string; href: string };
  secondaryCta?: { label: string; href: string };
  availability?: string;
  clientsLabel?: string;
  clients?: string[];
  className?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;

/* Two quiet wordmark treatments so the row reads like real logos without any serif or mono fonts. */
const WORDMARK_STYLES = [
  "font-semibold tracking-[-0.02em]",
  "text-[0.8em] font-medium uppercase tracking-[0.16em]",
  "font-semibold tracking-[-0.04em] lowercase",
  "text-[0.82em] font-semibold uppercase tracking-[0.06em]",
];

function renderLine(line: string) {
  const parts = line.split(/(\*[^*]+\*)/g).filter(Boolean);
  return parts.map((p, i) =>
    p.startsWith("*") && p.endsWith("*") ? (
      <span key={i} className="text-muted-foreground">
        {p.slice(1, -1)}
      </span>
    ) : (
      <React.Fragment key={i}>{p}</React.Fragment>
    ),
  );
}

export function HeroAgency({
  eyebrow = "Northline Studio, Denver",
  headline = ["We build brands", "people *remember*."],
  description = "Strategy, identity and conversion first websites for ambitious local businesses and real estate teams. Senior people only. Live in six weeks, not six months.",
  primaryCta = { label: "Book a call", href: "#book" },
  secondaryCta = { label: "See our work", href: "#work" },
  availability = "Two project openings for Q1",
  clientsLabel = "Trusted by",
  clients = [
    "Summit Roofing",
    "Mile High Dental",
    "Alder & Vine",
    "Compass Ridge",
    "Peak Realty",
    "Ironwood",
    "Redline Auto",
    "Larimer Coffee",
  ],
  className,
}: HeroAgencyProps) {
  const reduce = useReducedMotion();

  return (
    <section className={cn("relative isolate overflow-hidden bg-background text-foreground", className)}>
      <div className="mx-auto flex max-w-[1120px] flex-col items-center px-6 pb-16 pt-20 text-center sm:pb-20 sm:pt-28 lg:pt-32">
        <motion.p
          initial={reduce ? false : { opacity: 0, y: 8 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, ease: EASE }}
          className="text-[13px] font-medium uppercase tracking-[0.08em] text-muted-foreground"
        >
          {eyebrow}
        </motion.p>

        <h1 className="mt-6 max-w-[14ch] text-balance text-[44px] font-semibold leading-[1] tracking-[-0.045em] sm:text-[72px] lg:text-[96px]">
          {headline.map((line, i) => (
            <span key={i} className="block overflow-hidden pb-[0.08em]">
              <motion.span
                className="block"
                initial={reduce ? { opacity: 0 } : { y: "100%" }}
                animate={reduce ? { opacity: 1 } : { y: "0%" }}
                transition={{ duration: 0.9, delay: 0.05 + i * 0.1, ease: EASE }}
              >
                {renderLine(line)}
              </motion.span>
            </span>
          ))}
        </h1>

        <motion.p
          initial={reduce ? false : { opacity: 0, y: 12 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8, delay: 0.35, ease: EASE }}
          className="mt-6 max-w-[640px] text-pretty text-[17px] leading-[1.5] text-muted-foreground sm:text-[21px] sm:leading-[1.4]"
        >
          {description}
        </motion.p>

        <motion.div
          initial={reduce ? false : { opacity: 0, y: 12 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.8, delay: 0.45, ease: EASE }}
          className="mt-9 flex flex-col items-center gap-5"
        >
          <div className="flex flex-wrap items-center justify-center gap-3">
            <a
              href={primaryCta.href}
              className="group inline-flex h-12 items-center gap-2 rounded-full bg-brand px-6 text-[15px] font-medium text-brand-foreground transition-colors hover:bg-brand/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
            >
              {primaryCta.label}
              <ArrowRight className="size-4 transition-transform duration-300 group-hover:translate-x-0.5" aria-hidden />
            </a>
            <a
              href={secondaryCta.href}
              className="inline-flex h-12 items-center rounded-full bg-muted px-6 text-[15px] font-medium text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring dark:hover:bg-foreground/[0.12]"
            >
              {secondaryCta.label}
            </a>
          </div>
          {availability && (
            <span className="flex items-center gap-2 text-sm text-muted-foreground">
              <span className="inline-flex size-1.5 rounded-full bg-success" aria-hidden />
              {availability}
            </span>
          )}
        </motion.div>
      </div>

      {/* Client marquee */}
      <motion.div
        initial={reduce ? false : { opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ duration: 1, delay: 0.7 }}
        className="mx-auto flex max-w-[1120px] flex-col gap-4 px-6 pb-20 sm:pb-24 md:flex-row md:items-center md:gap-10"
      >
        <p className="shrink-0 text-center text-[13px] font-medium text-muted-foreground md:text-left">{clientsLabel}</p>
        <div
          className="group relative flex overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]"
          style={{ ["--marquee-gap" as string]: "3.5rem", ["--marquee-duration" as string]: "44s" }}
        >
          <ul className="flex w-max shrink-0 animate-marquee items-center gap-[var(--marquee-gap)] group-hover:[animation-play-state:paused]">
            {[...clients, ...clients].map((c, i) => (
              <li
                key={i}
                aria-hidden={i >= clients.length}
                className={cn(
                  "whitespace-nowrap text-xl text-foreground/40 transition-colors hover:text-foreground",
                  WORDMARK_STYLES[i % clients.length % WORDMARK_STYLES.length],
                )}
              >
                {c}
              </li>
            ))}
          </ul>
        </div>
      </motion.div>
    </section>
  );
}

export default HeroAgency;
```
