Component · Testimonials
Testimonial Carousel
Accessible Vue 3 testimonial carousel with prev/next, dots and autoplay that pauses on hover or focus and honours reduced motion. Tailwind CSS.
Masonry grid of testimonial cards for Vue 3 with initials avatars, optional star ratings and highlighted quotes. Tailwind CSS, dark mode.
by Vueplayv1.0.0Updated 3 hours ago
A social proof section that fits quotes of any length. Cards flow into one, two or three masonry columns using CSS columns, so a two-line quote and a paragraph sit together without awkward gaps.
Each card shows the quote, the person's name, role and company, and an initials avatar tinted from a stable hash of the name, so you don't need headshots. Mark one or two quotes as featured to give them a warm background and larger type. Star ratings are optional per card and announced as "Rated 4 out of 5".
Pass your own testimonials array, turn ratings off with showRating, and use the footer slot for a link to case studies.
<script setup lang="ts">
export interface Testimonial {
quote: string
name: string
role: string
company: string
/** 1 to 5. Omit to hide the stars on this card. */
rating?: number
/** Highlighted card with larger text. Use for one or two standout quotes. */
featured?: boolean
}
withDefaults(defineProps<{
eyebrow?: string
heading?: string
subheading?: string
testimonials?: Testimonial[]
/** Show star ratings on cards that have one. */
showRating?: boolean
}>(), {
eyebrow: 'Loved by busy teams',
heading: 'Less chasing, more shipping',
subheading: 'Over 2,400 teams moved their weekly planning to Tidewell. Here is what a few of them noticed first.',
showRating: true,
testimonials: () => [
{ quote: 'We replaced three spreadsheets and a standing meeting with one board. Our Monday planning went from an hour to fifteen minutes, and nobody misses the old way.', name: 'Maren Okafor', role: 'Head of Product', company: 'Fieldnote', rating: 5, featured: true },
{ quote: 'Setup took an afternoon. The import mapped our old statuses almost perfectly.', name: 'Tomas Reyes', role: 'Engineering Manager', company: 'Brightloop', rating: 5 },
{ quote: 'The weekly digest is the only status email my leadership team actually reads.', name: 'Priya Lindqvist', role: 'COO', company: 'Harbor & Pine', rating: 5 },
{ quote: 'I like that it stays out of the way. Keyboard shortcuts for everything, no clutter, and it is fast even on our biggest projects with thousands of tasks.', name: 'Jonah Whitfield', role: 'Staff Engineer', company: 'Quillstack', rating: 4 },
{ quote: 'Our clients get a read-only view of progress, so the "any update?" emails stopped almost overnight.', name: 'Aiko Brandt', role: 'Studio Lead', company: 'Northpaw Design', rating: 5 },
{ quote: 'Support answered a tricky permissions question in twenty minutes, on a Sunday. That sold me.', name: 'Samuel Adeyemi', role: 'IT Lead', company: 'Coralfin Health', rating: 5 },
{ quote: 'Finally a planning tool that the design team and the backend team both like using. That alone was worth the switch.', name: 'Elena Vasquez', role: 'VP Engineering', company: 'Lanternworks', rating: 5, featured: true },
],
})
const initials = (name: string) => name.split(/\s+/).filter(Boolean).slice(0, 2).map(p => p[0]!.toUpperCase()).join('')
// A stable tint per person so avatars feel varied without images.
const tints = [
'bg-amber-100 text-amber-800 dark:bg-amber-400/15 dark:text-amber-200',
'bg-sky-100 text-sky-800 dark:bg-sky-400/15 dark:text-sky-200',
'bg-emerald-100 text-emerald-800 dark:bg-emerald-400/15 dark:text-emerald-200',
'bg-rose-100 text-rose-800 dark:bg-rose-400/15 dark:text-rose-200',
'bg-violet-100 text-violet-800 dark:bg-violet-400/15 dark:text-violet-200',
]
function tint(name: string) {
let h = 0
for (const ch of name) h = (h * 31 + ch.charCodeAt(0)) >>> 0
return tints[h % tints.length]
}
</script>
<template>
<section class="px-4 py-20 sm:px-6 sm:py-24 lg:px-8">
<div class="mx-auto max-w-2xl text-center">
<p v-if="eyebrow" class="text-sm font-semibold text-amber-700 dark:text-amber-400">{{ eyebrow }}</p>
<h2 class="mt-2 text-3xl font-bold tracking-tight text-balance text-gray-900 sm:text-4xl dark:text-white">{{ heading }}</h2>
<p class="mt-4 text-lg text-pretty text-gray-600 dark:text-gray-400">{{ subheading }}</p>
</div>
<ul class="mx-auto mt-14 max-w-6xl columns-1 gap-6 sm:columns-2 lg:columns-3" role="list">
<li v-for="t in testimonials" :key="t.name" class="mb-6 break-inside-avoid">
<figure
class="flex flex-col rounded-2xl p-6 ring-1 sm:p-7"
:class="t.featured
? 'bg-amber-50 ring-amber-200 dark:bg-amber-400/[0.06] dark:ring-amber-400/20'
: 'bg-white shadow-sm ring-gray-200 dark:bg-gray-900 dark:shadow-none dark:ring-white/10'"
>
<div v-if="showRating && t.rating" class="mb-4 flex gap-0.5" role="img" :aria-label="`Rated ${t.rating} out of 5`">
<svg
v-for="n in 5"
:key="n"
viewBox="0 0 20 20"
aria-hidden="true"
class="size-4.5"
:class="n <= t.rating ? 'fill-amber-400' : 'fill-gray-200 dark:fill-white/15'"
>
<path d="M10 1.8l2.47 5.01 5.53.8-4 3.9.94 5.5L10 14.42 5.06 17l.94-5.5-4-3.9 5.53-.8L10 1.8Z" />
</svg>
</div>
<blockquote
class="text-pretty text-gray-800 dark:text-gray-200"
:class="t.featured ? 'text-lg/8 font-medium' : 'text-base/7'"
>
<p>“{{ t.quote }}”</p>
</blockquote>
<figcaption class="mt-6 flex items-center gap-3">
<span class="grid size-10 shrink-0 place-items-center rounded-full text-sm font-semibold" :class="tint(t.name)" aria-hidden="true">
{{ initials(t.name) }}
</span>
<span class="min-w-0">
<span class="block text-sm font-semibold text-gray-900 dark:text-white">{{ t.name }}</span>
<span class="block text-sm text-gray-600 dark:text-gray-400">{{ t.role }}, {{ t.company }}</span>
</span>
</figcaption>
</figure>
</li>
</ul>
<div v-if="$slots.footer" class="mt-8 text-center">
<slot name="footer" />
</div>
</section>
</template>
| Prop | Type | Default | Description |
|---|---|---|---|
| eyebrow | string | 'Loved by busy teams' | Small label above the heading. Empty string hides it. |
| heading | string | 'Less chasing, more shipping' | Section heading. |
| subheading | string | 'Over 2,400 teams…' | Supporting text under the heading. |
| testimonials | Testimonial[] | 7 sample quotes | Quotes with name, role, company, optional rating (1-5) and optional featured flag. |
| showRating | boolean | true | Show star ratings on cards that have one. |
| Slot | Description |
|---|---|
| footer | Content centred below the grid, e.g. a link to case studies. |
Component · Testimonials
Accessible Vue 3 testimonial carousel with prev/next, dots and autoplay that pauses on hover or focus and honours reduced motion. Tailwind CSS.
Component · Feature sections
"Trusted by" logo cloud for Vue 3 with a bordered grid or an endless marquee, using text wordmarks and SVG marks. Tailwind CSS, dark mode, reduced motion.
Component · Hero sections
Split Vue 3 hero section with copy and two CTAs on the left and a Tailwind-drawn app window mockup on the right. Dark mode, typed props, no images.
Component · Feature sections
Bento-style Vue 3 feature grid with wide and tall tiles and small CSS-drawn visuals like maps, timelines and code. Tailwind CSS, dark mode, typed tiles.