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.
Last updated
---
/**
* 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>
Show codeHide 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.