Zoeken...  ⌘K GitHub

TestimonialsGrid Social Proof

Klantreviews in grid met sterrating en avatar.

/testimonials-grid
src/components/social-proof/TestimonialsGrid.astro
---
/**
 * TestimonialsGrid
 * Reviews/testimonials in een masonry-stijl grid of rij.
 *
 * Props:
 * - headline?: string
 * - testimonials: Array<Testimonial>
 * - columns?: 2 | 3
 */
interface Testimonial {
  quote: string;
  author: string;
  role?: string;
  company?: string;
  avatar?: string;
  rating?: 1 | 2 | 3 | 4 | 5;
}
interface Props {
  headline?: string;
  testimonials: Testimonial[];
  columns?: 2 | 3;
}

const { headline, testimonials, columns = 3 } = Astro.props;
---

<section class={`testi testi--cols-${columns}`} data-testi>
  <div class="testi__inner">
    {headline && <h2 class="testi__headline">{headline}</h2>}
    <ul class="testi__grid">
      {testimonials.map(t => (
        <li class="testi__card">
          {t.rating && (
            <div class="testi__stars" aria-label={`${t.rating} van 5 sterren`}>
              {'★'.repeat(t.rating)}{'☆'.repeat(5 - t.rating)}
            </div>
          )}
          <blockquote class="testi__quote">"{t.quote}"</blockquote>
          <footer class="testi__author">
            {t.avatar && (
              <img src={t.avatar} alt={t.author} class="testi__avatar" loading="lazy" />
            )}
            <div>
              <cite class="testi__name">{t.author}</cite>
              {(t.role || t.company) && (
                <p class="testi__meta">{[t.role, t.company].filter(Boolean).join(', ')}</p>
              )}
            </div>
          </footer>
        </li>
      ))}
    </ul>
  </div>
</section>

<style>
  .testi { padding: 5rem 1.5rem; background: var(--color-bg); }
  .testi__inner { max-width: 1280px; margin: 0 auto; }
  .testi__headline {
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    text-align: center;
    margin: 0 0 3rem;
  }
  .testi__grid {
    list-style: none;
    margin: 0; padding: 0;
    display: grid;
    gap: 1.5rem;
    grid-template-columns: 1fr;
  }
  @media (min-width: 640px) {
    .testi--cols-2 .testi__grid,
    .testi--cols-3 .testi__grid { grid-template-columns: 1fr 1fr; }
  }
  @media (min-width: 960px) {
    .testi--cols-3 .testi__grid { grid-template-columns: repeat(3, 1fr); }
  }
  .testi__card {
    padding: 1.75rem;
    background: color-mix(in srgb, var(--color-text) 4%, transparent);
    border-radius: calc(var(--radius) * 2);
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  .testi__stars { color: #f59e0b; font-size: 0.875rem; letter-spacing: 0.05em; }
  .testi__quote {
    font-size: 1rem;
    line-height: 1.65;
    color: var(--color-text);
    margin: 0;
    flex: 1;
  }
  .testi__author {
    display: flex;
    align-items: center;
    gap: 0.75rem;
  }
  .testi__avatar {
    width: 2.5rem; height: 2.5rem;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
  }
  .testi__name {
    font-style: normal;
    font-weight: 700;
    font-size: 0.9375rem;
    display: block;
  }
  .testi__meta {
    font-size: 0.8125rem;
    color: var(--color-muted);
    margin: 0;
  }
</style>

Props

Prop Type Default Beschrijving
testimonials * Testimonial[] Array van testimonials. Zie type in component.
headline string Sectie headline
columns 2 | 3 3 Aantal kolommen

* = verplicht