Image card 04
A heading with actions over a scrolling row of people with square photos and bios.
Last updated
---
/**
* Image card 04: A heading with actions over a scrolling row of people with square photos and bios.
*
* @block team
* @order 8
*/
import Section from '@/components/apogee/Section/Section.astro'
import SectionHeader from '@/components/apogee/SectionHeader/SectionHeader.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Image from '@/components/apogee/Image/Image.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Carousel from '@/components/apogee/Carousel/Carousel.astro'
import { plain } from '@/components/apogee/sections/_shared/spine.ts'
import type { TeamContent } from '@/components/apogee/sections/_shared/team.ts'
/** @composable */
interface Props extends TeamContent {}
const {
eyebrow,
heading = "We're a fast-growing team",
description = "We're always looking for passionate, curious people to join us.",
label = 'Team',
primaryAction = { label: 'Open positions', href: '#' },
secondaryAction = { label: 'About us', href: '#' },
items = [
{ name: 'Maya Okafor', role: 'Founder and CEO', avatar: 'https://picsum.photos/id/1027/600/600', bio: 'Started the company after ten years building tools for small teams.' },
{ name: 'Tomas Lindqvist', role: 'Engineering Manager', avatar: 'https://picsum.photos/id/856/600/600', bio: 'Keeps the team shipping and the pager quiet.' },
{ name: 'Priya Raman', role: 'Product Manager', avatar: 'https://picsum.photos/id/832/600/600', bio: 'Turns customer calls into roadmaps people can read.' },
{ name: 'Jonah Weiss', role: 'Frontend Developer', avatar: 'https://picsum.photos/id/91/600/600', bio: 'Cares about fast pages and buttons that feel right.' },
{ name: 'Lucía Ortega', role: 'Backend Developer', avatar: 'https://picsum.photos/id/64/600/600', bio: 'Builds the APIs and keeps the data honest.' },
{ name: 'Samir Haddad', role: 'Product Designer', avatar: 'https://picsum.photos/id/375/600/600', bio: 'Sketches first, then sweats the last pixel.' },
{ name: 'Hana Kobayashi', role: 'UX Researcher', avatar: 'https://picsum.photos/id/823/600/600', bio: 'Talks to customers so the rest of us can listen.' },
{ name: 'Eli Brennan', role: 'Customer Success', avatar: 'https://picsum.photos/id/836/600/600', bio: 'Answers every question as if it were the first.' },
],
} = Astro.props
---
<Section spacing="large">
<div class="flex flex-col gap-12 md:gap-16">
<SectionHeader eyebrow={eyebrow ?? undefined} heading={plain(heading)} description={description ?? undefined} size="large">
{(primaryAction || secondaryAction) && <Fragment slot="actions"><div class="flex flex-col gap-3 sm:flex-row-reverse sm:justify-end">
{primaryAction && <Button href={primaryAction.href} size="large">{primaryAction.label}</Button>}
{secondaryAction && <Button href={secondaryAction.href} size="large" variant="outline" color="neutral">{secondaryAction.label}</Button>}
</div></Fragment>}
</SectionHeader>
<Carousel label={label} itemWidth="17.5rem" controlsPosition="below" gap={8}>
{items.map((p) => (
<div class="flex flex-col gap-5">{p.avatar && <Image src={p.avatar} alt="" width={600} height={600} fit="cover" class="aspect-square w-full" />}<div class="flex flex-col"><Heading as="h3" size="h6">{p.name}</Heading><p class="text-primary">{p.role}</p>{p.bio && <p class="mt-2 text-neutral-faded">{p.bio}</p>}</div></div>
))}
</Carousel>
</div>
</Section>
Show codeHide code
---
/**
* Image card 04: A heading with actions over a scrolling row of people with square photos and bios.
*
* @block team
* @order 8
*/
import Section from '@/components/apogee/Section/Section.astro'
import SectionHeader from '@/components/apogee/SectionHeader/SectionHeader.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Image from '@/components/apogee/Image/Image.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Carousel from '@/components/apogee/Carousel/Carousel.astro'
import { plain } from '@/components/apogee/sections/_shared/spine.ts'
import type { TeamContent } from '@/components/apogee/sections/_shared/team.ts'
/** @composable */
interface Props extends TeamContent {}
const {
eyebrow,
heading = "We're a fast-growing team",
description = "We're always looking for passionate, curious people to join us.",
label = 'Team',
primaryAction = { label: 'Open positions', href: '#' },
secondaryAction = { label: 'About us', href: '#' },
items = [
{ name: 'Maya Okafor', role: 'Founder and CEO', avatar: 'https://picsum.photos/id/1027/600/600', bio: 'Started the company after ten years building tools for small teams.' },
{ name: 'Tomas Lindqvist', role: 'Engineering Manager', avatar: 'https://picsum.photos/id/856/600/600', bio: 'Keeps the team shipping and the pager quiet.' },
{ name: 'Priya Raman', role: 'Product Manager', avatar: 'https://picsum.photos/id/832/600/600', bio: 'Turns customer calls into roadmaps people can read.' },
{ name: 'Jonah Weiss', role: 'Frontend Developer', avatar: 'https://picsum.photos/id/91/600/600', bio: 'Cares about fast pages and buttons that feel right.' },
{ name: 'Lucía Ortega', role: 'Backend Developer', avatar: 'https://picsum.photos/id/64/600/600', bio: 'Builds the APIs and keeps the data honest.' },
{ name: 'Samir Haddad', role: 'Product Designer', avatar: 'https://picsum.photos/id/375/600/600', bio: 'Sketches first, then sweats the last pixel.' },
{ name: 'Hana Kobayashi', role: 'UX Researcher', avatar: 'https://picsum.photos/id/823/600/600', bio: 'Talks to customers so the rest of us can listen.' },
{ name: 'Eli Brennan', role: 'Customer Success', avatar: 'https://picsum.photos/id/836/600/600', bio: 'Answers every question as if it were the first.' },
],
} = Astro.props
---
<Section spacing="large">
<div class="flex flex-col gap-12 md:gap-16">
<SectionHeader eyebrow={eyebrow ?? undefined} heading={plain(heading)} description={description ?? undefined} size="large">
{(primaryAction || secondaryAction) && <Fragment slot="actions"><div class="flex flex-col gap-3 sm:flex-row-reverse sm:justify-end">
{primaryAction && <Button href={primaryAction.href} size="large">{primaryAction.label}</Button>}
{secondaryAction && <Button href={secondaryAction.href} size="large" variant="outline" color="neutral">{secondaryAction.label}</Button>}
</div></Fragment>}
</SectionHeader>
<Carousel label={label} itemWidth="17.5rem" controlsPosition="below" gap={8}>
{items.map((p) => (
<div class="flex flex-col gap-5">{p.avatar && <Image src={p.avatar} alt="" width={600} height={600} fit="cover" class="aspect-square w-full" />}<div class="flex flex-col"><Heading as="h3" size="h6">{p.name}</Heading><p class="text-primary">{p.role}</p>{p.bio && <p class="mt-2 text-neutral-faded">{p.bio}</p>}</div></div>
))}
</Carousel>
</div>
</Section>Add
npx @ava-ui/apogee add team/image-card-04
This copies the block to src/components/apogee/blocks/team/ with everything it uses: Button, Carousel, Heading, Image, Section, SectionHeader. 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.
Accessibility
- The heading is an
h2by default, so the block slots into a page under itsh1; item titles are one level down. - The carousel is a named region; its arrows are buttons and it scrolls by swipe or keyboard too.
- Photos that only decorate have an empty
alt; give yours one when it carries meaning.