A heading and round button at the start; a menu, social links and a release-video card at the end over a newsletter signup; then the copyright and legal links. Always dark.

Last updated

Open block
Show code
---
/**
 * Video newsletter 01: A heading and round button at the start; a menu, social links and a release-video card at the end over a newsletter signup; then the copyright and legal links. Always dark.
 *
 * @block footers
 * @order 45
 */
import { ArrowRight, Play } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Input from '@/components/apogee/Input/Input.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { accentRuns } from '@/components/apogee/sections/_shared/spine.ts'
import { socialIcons, type FooterContent } from '@/components/apogee/sections/_shared/footers.ts'

/** @composable */
interface Props extends FooterContent {
  /** The video card's caption, running time and link. @category content */
  film?: { label: string; duration?: string; href: string } | null
}

const {
  heading = 'Explore *beyond limits.*',
  primaryAction = { label: 'Pre-order now', href: '#' },
  newsletterHeading = 'Stay up to date with our newsletter',
  submitLabel = 'Subscribe',
  columns = [{ heading: 'Menu', links: [{ label: 'Home', href: '#' }, { label: 'The bike', href: '#' }, { label: 'Technical specs', href: '#' }, { label: 'Our story', href: '#' }, { label: 'Support', href: '#' }, { label: 'Careers', href: '#' }, { label: 'Press kit', href: '#' }] }],
  copyright = '© 2026 Radiant',
  legal = [{ label: 'Terms', href: '#' }, { label: 'Privacy policy', href: '#' }, { label: 'Privacy settings', href: '#' }, { label: 'Accessibility', href: '#' }],
  social = [
    { label: 'Photos', href: '#', icon: 'camera' },
    { label: 'Updates', href: '#', icon: 'at-sign' },
    { label: 'Videos', href: '#', icon: 'video' },
    { label: 'Feed', href: '#', icon: 'rss' },
  ],
  film = { label: 'Play the release film', duration: '2:56', href: '#' },
} = Astro.props
---

<Section as="footer" spacing="medium" theme="dark">
  <div class="grid gap-12 lg:grid-cols-2">
    <div class="flex flex-col items-start gap-6">
      {heading && <Heading as="h2" size="h1" class="font-normal">{accentRuns(heading).map((r) => (r.accent ? <span class="block">{r.text}</span> : r.text))}</Heading>}
      {primaryAction && <Button href={primaryAction.href} size="large" rounded variant="outline" color="neutral">{primaryAction.label}<ArrowRight slot="end" class="rtl:-scale-x-100" aria-hidden="true" /></Button>}
    </div>

    <div class="flex flex-col gap-16">
      <div class="grid gap-10 sm:grid-cols-3">
        {columns.map((c) => (
          <nav aria-label={c.heading}>
            <p class="font-mono text-xs uppercase tracking-widest text-neutral-faded">{c.heading}</p>
            <ul class="mt-6 flex list-none flex-col gap-2 p-0">{c.links.map((l) => <li><Link href={l.href} color="neutral" underline="hover">{l.label}</Link></li>)}</ul>
          </nav>
        ))}
        {social && <nav aria-label="Social">
          <p class="font-mono text-xs uppercase tracking-widest text-neutral-faded">Social</p>
          <ul class="mt-6 flex list-none flex-col gap-2.5 p-0">{social.map((s) => { const Icon = socialIcons[s.icon]; return <li><Link href={s.href} color="neutral" underline="hover" class="inline-flex items-center gap-2"><Icon class="size-4" aria-hidden="true" />{s.label}</Link></li> })}</ul>
        </nav>}
        {film && <a href={film.href} class="group flex flex-col gap-2">
          <span class="relative block overflow-hidden rounded-md">
            <img src="https://picsum.photos/id/1071/480/300" alt="" loading="lazy" class="aspect-8/5 w-full object-cover transition-transform duration-(--duration-medium) group-hover:scale-105 motion-reduce:transition-none" />
            <span class="absolute inset-0 grid place-items-center"><span class="grid size-10 place-items-center rounded-full bg-page/80 backdrop-blur"><Play class="size-4" aria-hidden="true" /></span></span>
          </span>
          <span class="text-sm">{film.label} {film.duration && <span class="text-neutral-faded tabular-nums">{film.duration}</span>}</span>
        </a>}
      </div>

      <form class="flex flex-col gap-4" onsubmit="return false">
        <label for="video-newsletter-email" class:list={['text-2xl', !newsletterHeading && 'sr-only']}>{newsletterHeading ?? 'Email'}</label>
        <div class="flex max-w-lg gap-2">
          <Input id="video-newsletter-email" type="email" name="email" placeholder="Email address" autocomplete="email" size="large" class="flex-1" />
          <Button type="submit" size="large" rounded color="neutral" variant="outline">{submitLabel}</Button>
        </div>
      </form>
    </div>
  </div>

  <div class="mt-16 flex flex-col gap-4 border-t border-neutral-faded pt-6 text-sm md:flex-row md:justify-between">
    <p>{copyright}</p>
    {legal && <ul class="flex list-none flex-wrap gap-x-6 gap-y-2 p-0">{legal.map((l) => <li><Link href={l.href} color="neutral" underline="hover">{l.label}</Link></li>)}</ul>}
  </div>
</Section>

Add

npx @ava-ui/apogee add footers/video-newsletter-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.