Card 03
A centred heading and a wide photo, then open positions grouped by department.
Last updated
---
/**
* Card 03: A centred heading and a wide photo, then open positions grouped by department.
*
* @block careers
* @order 7
*/
import Section from '@/components/apogee/Section/Section.astro'
import SectionHeader from '@/components/apogee/SectionHeader/SectionHeader.astro'
import JobCard from '@/components/apogee/JobCard/JobCard.astro'
import { plain } from '@/components/apogee/sections/_shared/spine.ts'
import { type CareersContent, type Job } from '@/components/apogee/sections/_shared/careers.ts'
import Image from '@/components/apogee/Image/Image.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
const demoJobs: Job[] = [
{
title: 'Product Designer',
href: '#',
description: "We're looking for a mid-level product designer to join our team.",
department: 'Design',
location: 'Remote',
type: 'Full-time',
salary: '80k – 100k',
badge: { label: 'Design', color: 'primary' },
},
{
title: 'Engineering Manager',
href: '#',
description: "We're looking for an experienced engineering manager to lead a product team.",
department: 'Software Development',
location: 'Remote',
type: 'Full-time',
salary: '120k – 150k',
badge: { label: 'Software', color: 'neutral' },
},
{
title: 'Customer Success Manager',
href: '#',
description: "Help our customers get the most out of the product, from first call to renewal.",
department: 'Customer Success',
location: 'Lisbon, Portugal',
type: 'Full-time',
salary: '70k – 90k',
badge: { label: 'Customer success', color: 'positive' },
},
{
title: 'UX Researcher',
href: '#',
description: "Talk to the people who use what we build and bring what you learn back.",
department: 'Design',
location: 'Remote',
type: 'Contract',
salary: '75k – 95k',
badge: { label: 'Design', color: 'primary' },
},
]
/** @composable */
interface Props extends CareersContent {}
const {
eyebrow = 'Careers',
heading = "We're looking for talented people",
description = "We're a remote team spread all across the world. Join us!",
items = demoJobs,
media = { src: 'https://picsum.photos/seed/apogee-careers-2/1600/700', alt: '', width: 1600, height: 700 },
} = Astro.props
// Jobs grouped by department, in order of first appearance.
const groups = [...new Set(items.flatMap((job) => job.department ?? []))].map((department) => ({ department, jobs: items.filter((job) => job.department === department) }))
---
<Section spacing="large">
<div class="mx-auto flex max-w-3xl flex-col items-center gap-4 text-center">
{eyebrow && <Badge color="primary" rounded>{eyebrow}</Badge>}
<SectionHeader heading={plain(heading)} description={description ?? undefined} align="center" />
</div>
{media && <Image src={media.src} alt={media.alt} width={media.width} height={media.height} fit="cover" radius="xl" class="mt-12 h-60 w-full md:mt-16 md:h-[35rem]" />}
<ul class="mx-auto mt-12 flex max-w-3xl list-none flex-col gap-12 p-0 md:mt-16 md:gap-16">
{groups.map((g) => (
<li>
<Heading as="h3" size="h5">{g.department}</Heading>
<ul class="mt-5 flex list-none flex-col gap-4 p-0 md:gap-6">
{g.jobs.map((job) => <li><JobCard job={{ ...job, department: undefined }} variant="card" headingLevel={4} /></li>)}
</ul>
</li>
))}
</ul>
</Section>
Show codeHide code
---
/**
* Card 03: A centred heading and a wide photo, then open positions grouped by department.
*
* @block careers
* @order 7
*/
import Section from '@/components/apogee/Section/Section.astro'
import SectionHeader from '@/components/apogee/SectionHeader/SectionHeader.astro'
import JobCard from '@/components/apogee/JobCard/JobCard.astro'
import { plain } from '@/components/apogee/sections/_shared/spine.ts'
import { type CareersContent, type Job } from '@/components/apogee/sections/_shared/careers.ts'
import Image from '@/components/apogee/Image/Image.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
const demoJobs: Job[] = [
{
title: 'Product Designer',
href: '#',
description: "We're looking for a mid-level product designer to join our team.",
department: 'Design',
location: 'Remote',
type: 'Full-time',
salary: '80k – 100k',
badge: { label: 'Design', color: 'primary' },
},
{
title: 'Engineering Manager',
href: '#',
description: "We're looking for an experienced engineering manager to lead a product team.",
department: 'Software Development',
location: 'Remote',
type: 'Full-time',
salary: '120k – 150k',
badge: { label: 'Software', color: 'neutral' },
},
{
title: 'Customer Success Manager',
href: '#',
description: "Help our customers get the most out of the product, from first call to renewal.",
department: 'Customer Success',
location: 'Lisbon, Portugal',
type: 'Full-time',
salary: '70k – 90k',
badge: { label: 'Customer success', color: 'positive' },
},
{
title: 'UX Researcher',
href: '#',
description: "Talk to the people who use what we build and bring what you learn back.",
department: 'Design',
location: 'Remote',
type: 'Contract',
salary: '75k – 95k',
badge: { label: 'Design', color: 'primary' },
},
]
/** @composable */
interface Props extends CareersContent {}
const {
eyebrow = 'Careers',
heading = "We're looking for talented people",
description = "We're a remote team spread all across the world. Join us!",
items = demoJobs,
media = { src: 'https://picsum.photos/seed/apogee-careers-2/1600/700', alt: '', width: 1600, height: 700 },
} = Astro.props
// Jobs grouped by department, in order of first appearance.
const groups = [...new Set(items.flatMap((job) => job.department ?? []))].map((department) => ({ department, jobs: items.filter((job) => job.department === department) }))
---
<Section spacing="large">
<div class="mx-auto flex max-w-3xl flex-col items-center gap-4 text-center">
{eyebrow && <Badge color="primary" rounded>{eyebrow}</Badge>}
<SectionHeader heading={plain(heading)} description={description ?? undefined} align="center" />
</div>
{media && <Image src={media.src} alt={media.alt} width={media.width} height={media.height} fit="cover" radius="xl" class="mt-12 h-60 w-full md:mt-16 md:h-[35rem]" />}
<ul class="mx-auto mt-12 flex max-w-3xl list-none flex-col gap-12 p-0 md:mt-16 md:gap-16">
{groups.map((g) => (
<li>
<Heading as="h3" size="h5">{g.department}</Heading>
<ul class="mt-5 flex list-none flex-col gap-4 p-0 md:gap-6">
{g.jobs.map((job) => <li><JobCard job={{ ...job, department: undefined }} variant="card" headingLevel={4} /></li>)}
</ul>
</li>
))}
</ul>
</Section>Add
npx @ava-ui/apogee add careers/card-03
This copies the block to src/components/apogee/blocks/careers/ with everything it uses: Badge, Heading, Image, JobCard, 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. - Each position is one link: the title stretches over the card. Its details are a description list (Location, Type, Salary) for screen readers.
- Photos that only decorate have an empty
alt; give yours one when it carries meaning.
Related
JobCardOne open position; title with a status badge, a short description and its location, type and salary, as a list row or a card.
TabsSwitch between panels of related content in the same space.
SelectA labeled, native <select> — full keyboard and mobile picker support for free — or a custom listbox with rich options over the same form control.