Skip to content

Agency footer

An oversized invitation, two office addresses, and a short studio link row on an inverted field.

Footersstarterfooteragencycontactinvertedaddress

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

components/blocks/footers/agency.tsx

This exact file renders the preview above.

import Link from 'next/link'
import { ArrowUpRight } from 'lucide-react'
import { Container } from '@/components/ui/layout'

/**
 * Agency footer
 *
 * The contact block *is* the footer. An oversized "start a conversation" line,
 * two offices, and a short link row — a studio site's footer should read like
 * the last line of a letter, not a sitemap.
 */
const offices = [
  { city: 'London', line1: '18 Ashfield Row', line2: 'EC2A 4NE', email: 'london@quarry.example' },
  { city: 'Lisbon', line1: 'Rua da Prata 42', line2: '1100-414', email: 'lisbon@quarry.example' },
]

const links = [
  { label: 'Work', href: '/starters/agency/preview/work' },
  { label: 'Services', href: '/starters/agency/preview/services' },
  { label: 'Journal', href: '/starters/agency/preview/journal' },
  { label: 'Contact', href: '/starters/agency/preview/contact' },
]

export default function AgencyFooter() {
  return (
    <footer className="border-t border-line bg-surface-inverse text-ink-inverse">
      <Container className="py-16">
        <Link
          href="/starters/agency/preview/contact"
          className="display-type inline-flex max-w-3xl items-start gap-3 text-3xl leading-tight font-semibold sm:text-4xl lg:text-5xl"
        >
          Start a conversation
          <ArrowUpRight className="mt-2 size-6 shrink-0 sm:size-8" aria-hidden="true" />
        </Link>

        <div className="mt-14 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
          {offices.map((office) => (
            <address key={office.city} className="text-sm not-italic opacity-80">
              <p className="label-caps mb-2 opacity-70">{office.city}</p>
              <p>{office.line1}</p>
              <p>{office.line2}</p>
              <a
                href={`mailto:${office.email}`}
                className="mt-2 inline-block underline underline-offset-4"
              >
                {office.email}
              </a>
            </address>
          ))}

          <nav aria-label="Studio" className="lg:col-start-4">
            <p className="label-caps mb-2 opacity-70">Studio</p>
            <ul className="flex flex-col gap-1.5">
              {links.map((link) => (
                <li key={link.href}>
                  <Link
                    href={link.href}
                    className="text-sm opacity-80 transition-opacity hover:opacity-100"
                  >
                    {link.label}
                  </Link>
                </li>
              ))}
            </ul>
          </nav>
        </div>

        <p className="mt-14 border-t border-white/15 pt-6 text-xs opacity-60">
          Quarry Studio — a fictional practice built to demonstrate Foundry.
        </p>
      </Container>
    </footer>
  )
}

Demo source — adapt to your project. Foundry is not published as a package.

Usage

For studios. The contact block *is* the footer — it should read like the last line of a letter, not a sitemap.

  • Use real `<address>` elements; they carry meaning and are styled to remove the browser italic.
  • Email links are `mailto:` and underlined, so the affordance is not colour-only.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Oversized CTA link
  • Office addresses
  • Inverted surface

Accessibility

Address semantics
Each office is an `<address>` with a heading-style label.
Inverted contrast
Foreground uses the ink-inverse token, so contrast survives palette changes.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.