Simple centered 03
Quotes from five companies, switched by tabs that show each company.
Last updated
---
/**
* Simple centered 03: Quotes from five companies, switched by tabs that show each company.
*
* @block testimonials
* @order 9
*/
import { Atom, Hexagon, Mountain, Sprout, Waves } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Avatar from '@/components/apogee/Avatar/Avatar.astro'
import Tabs from '@/components/apogee/Tabs/Tabs.astro'
import type { TestimonialsContent } from '@/components/apogee/sections/_shared/testimonials.ts'
/** @composable */
interface Props extends TestimonialsContent {
/** Which quote's tab is open first (0-based). @category layout */
selected?: number
}
const {
label = 'Customers',
items = [
{ quote: 'We shipped our redesign in three weeks instead of three months. The team still talks about it.', name: 'Amara Nwosu', role: 'Head of Operations, Lumen', avatar: 'https://picsum.photos/id/1027/192/192', company: 'Lumen' },
{ quote: 'Every page we build now looks like it belongs together. Our designers finally trust what ships.', name: 'Daniel Reyes', role: 'Engineering Lead, Quanta', avatar: 'https://picsum.photos/id/91/192/192', company: 'Quanta' },
{ quote: 'Support answered in minutes, and the fix was live the same afternoon.', name: 'Sofia Marchetti', role: 'Product Designer, Fieldwork', avatar: 'https://picsum.photos/id/832/192/192', company: 'Fieldwork' },
{ quote: 'It replaced four tools and a spreadsheet nobody wanted to open.', name: 'Kenji Watanabe', role: 'Founder, Northpeak', avatar: 'https://picsum.photos/id/856/192/192', company: 'Northpeak' },
{ quote: 'Onboarding took a day. Our new hires were shipping by the end of their first week.', name: 'Leah Goldberg', role: 'Customer Success Lead, Arcwave', avatar: 'https://picsum.photos/id/823/192/192', company: 'Arcwave' },
],
selected = 2,
} = Astro.props
// Tab icons are design: they cycle by position, the tab text is the company (or the person).
const icons = [Hexagon, Atom, Sprout, Mountain, Waves]
---
<Section spacing="large">
<div class="mx-auto max-w-4xl">
<Tabs label={label} tabs={items.map((t, i) => ({ label: t.company ?? t.name, icon: icons[i % icons.length]!, value: String(i) }))} value={String(selected)} class="[&_[role=tablist]]:justify-center">
{items.map((t, i) => (
<figure slot={`panel-${i}`} class="flex flex-col items-center gap-8 pt-10 text-center">
<blockquote class="text-2xl font-medium tracking-tight text-balance md:text-4xl"><p>“{t.quote}”</p></blockquote>
<figcaption class="flex flex-col items-center gap-3 text-center"><Avatar src={t.avatar} name={t.name} alt="" size="large" border /><span class="flex flex-col items-center"><span class="flex items-center gap-1 font-semibold">{t.name}</span>{t.role && <span class="text-sm text-neutral-faded">{t.role}</span>}</span></figcaption>
</figure>
))}
</Tabs>
</div>
</Section>
Show codeHide code
---
/**
* Simple centered 03: Quotes from five companies, switched by tabs that show each company.
*
* @block testimonials
* @order 9
*/
import { Atom, Hexagon, Mountain, Sprout, Waves } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Avatar from '@/components/apogee/Avatar/Avatar.astro'
import Tabs from '@/components/apogee/Tabs/Tabs.astro'
import type { TestimonialsContent } from '@/components/apogee/sections/_shared/testimonials.ts'
/** @composable */
interface Props extends TestimonialsContent {
/** Which quote's tab is open first (0-based). @category layout */
selected?: number
}
const {
label = 'Customers',
items = [
{ quote: 'We shipped our redesign in three weeks instead of three months. The team still talks about it.', name: 'Amara Nwosu', role: 'Head of Operations, Lumen', avatar: 'https://picsum.photos/id/1027/192/192', company: 'Lumen' },
{ quote: 'Every page we build now looks like it belongs together. Our designers finally trust what ships.', name: 'Daniel Reyes', role: 'Engineering Lead, Quanta', avatar: 'https://picsum.photos/id/91/192/192', company: 'Quanta' },
{ quote: 'Support answered in minutes, and the fix was live the same afternoon.', name: 'Sofia Marchetti', role: 'Product Designer, Fieldwork', avatar: 'https://picsum.photos/id/832/192/192', company: 'Fieldwork' },
{ quote: 'It replaced four tools and a spreadsheet nobody wanted to open.', name: 'Kenji Watanabe', role: 'Founder, Northpeak', avatar: 'https://picsum.photos/id/856/192/192', company: 'Northpeak' },
{ quote: 'Onboarding took a day. Our new hires were shipping by the end of their first week.', name: 'Leah Goldberg', role: 'Customer Success Lead, Arcwave', avatar: 'https://picsum.photos/id/823/192/192', company: 'Arcwave' },
],
selected = 2,
} = Astro.props
// Tab icons are design: they cycle by position, the tab text is the company (or the person).
const icons = [Hexagon, Atom, Sprout, Mountain, Waves]
---
<Section spacing="large">
<div class="mx-auto max-w-4xl">
<Tabs label={label} tabs={items.map((t, i) => ({ label: t.company ?? t.name, icon: icons[i % icons.length]!, value: String(i) }))} value={String(selected)} class="[&_[role=tablist]]:justify-center">
{items.map((t, i) => (
<figure slot={`panel-${i}`} class="flex flex-col items-center gap-8 pt-10 text-center">
<blockquote class="text-2xl font-medium tracking-tight text-balance md:text-4xl"><p>“{t.quote}”</p></blockquote>
<figcaption class="flex flex-col items-center gap-3 text-center"><Avatar src={t.avatar} name={t.name} alt="" size="large" border /><span class="flex flex-col items-center"><span class="flex items-center gap-1 font-semibold">{t.name}</span>{t.role && <span class="text-sm text-neutral-faded">{t.role}</span>}</span></figcaption>
</figure>
))}
</Tabs>
</div>
</Section>Add
npx @ava-ui/apogee add testimonials/simple-centered-03
This copies the block to src/components/apogee/blocks/testimonials/ with everything it uses: Avatar, Section, Tabs. 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 department tabs are a tab list with one panel each; without JS every panel shows, one after another.
Related
TestimonialsOne featured quote, or a grid of quote cards.
CarouselA horizontal scroll-snap track for browsing slides. Native scroll and swipe work with zero JS; arrows, dots, and autoplay are progressive enhancement.
RatingA star rating on native radios — pick a value on a scale, or display one read-only.