A small label, a two-line question and a contact card with a photo, email and phone; local time, place and social links at the end; giant initials and the copyright along the bottom. Always dark.

Last updated

Open block
Show code
---
/**
 * Contact card 01: A small label, a two-line question and a contact card with a photo, email and phone; local time, place and social links at the end; giant initials and the copyright along the bottom. Always dark.
 *
 * @block footers
 * @order 60
 */
import { CornerDownRight } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { accentRuns } from '@/components/apogee/sections/_shared/spine.ts'
import { type FooterContent } from '@/components/apogee/sections/_shared/footers.ts'

/** @composable */
interface Props extends FooterContent {
  /** The small label above the heading. @category content */
  eyebrow?: string | null
  /** The contact card under the heading. @category content */
  contact?: { email: string; phone?: string; href: string } | null
  /** @category content */
  localTime?: string | null
  /** @category content */
  location?: string | null
}

const {
  brand = 'MRST',
  eyebrow = '( Get in touch )',
  heading = 'Have a project in mind? *Let’s talk.*',
  contact = { email: 'hello@mirastudio.design', phone: '+44 20 7946 0321', href: '#' },
  localTime = '20:58 local time',
  location = 'Remote from Lisbon',
  columns = [{ heading: 'Social', links: [{ label: 'Portfolio', href: '#' }, { label: 'Updates', href: '#' }, { label: 'Photos', href: '#' }] }],
  copyright = '© 2026 Mira Studio',
} = Astro.props
---

<Section as="footer" spacing="medium" containerWidth="full" theme="dark">
  <div class="flex flex-col justify-between gap-12 md:flex-row">
    <div class="flex flex-col gap-8">
      {eyebrow && <p class="text-sm text-neutral-faded">{eyebrow}</p>}
      {heading && <Heading as="h2" size="h1" class="font-normal">{accentRuns(heading).map((r) => (r.accent ? <span class="block">{r.text}</span> : r.text))}</Heading>}
      {contact && <a href={contact.href} class="mt-6 flex items-center gap-4 self-start rounded-md bg-neutral-faded p-1.5 pe-6 hover:bg-neutral-faded-highlighted">
        <img src="https://picsum.photos/id/1012/96/96" alt="" class="size-12 rounded object-cover" />
        <span class="flex flex-col"><span class="text-lg">{contact.email}</span>{contact.phone && <span class="text-sm text-neutral-faded tabular-nums">{contact.phone}</span>}</span>
      </a>}
    </div>
    <div class="flex flex-col gap-10 md:w-56 md:pt-12">
      {(localTime || location) && <p class="flex flex-col">{localTime && <span class="text-neutral-faded tabular-nums">{localTime}</span>}{location}</p>}
      {columns.map((c) => (
        <nav aria-label={c.heading}>
          <p class="text-neutral-faded">{c.heading}</p>
          <ul class="mt-2 flex list-none flex-col gap-1 p-0">{c.links.map((l) => <li><Link href={l.href} color="neutral" underline="hover" class="inline-flex items-center gap-2"><CornerDownRight class="size-4 rtl:-scale-x-100" aria-hidden="true" />{l.label}</Link></li>)}</ul>
        </nav>
      ))}
    </div>
  </div>
  <div class="mt-24 flex flex-col-reverse gap-6 md:flex-row md:items-end md:justify-between">
    {copyright && <p class="text-sm text-neutral-faded">{copyright}</p>}
    <p class="text-[clamp(4rem,1rem+10vw,10rem)] font-normal leading-[0.8] tracking-tight" aria-hidden="true">{brand} ©</p>
  </div>
</Section>

Add

npx @ava-ui/apogee add footers/contact-card-01

Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.