Zoeken...  ⌘K GitHub

ContentTestimonial content

Klantreviews in grid of single-layout met sterren.

/content-testimonial
src/components/content/ContentTestimonial.astro
---
interface Testimonial {
  quote: string;
  name: string;
  role?: string;
  company?: string;
  avatar?: string;
  rating?: number;
}

interface Props {
  heading?: string;
  testimonials?: Testimonial[];
  layout?: "grid" | "single";
}

const {
  heading = "Wat onze klanten zeggen",
  layout = "grid",
  testimonials = [
    {
      quote: "BLURR heeft onze online omzet in 5 maanden verdubbeld. Ze denken mee, communiceren snel en leveren elke maand betere resultaten.",
      name: "Miriam van den Berg",
      role: "Marketing Director",
      company: "Dutchstore.nl",
      rating: 5,
    },
    {
      quote: "Eindelijk een bureau dat begrijpt wat B2B marketing inhoudt. Geen bullshit, gewoon leads die converteren.",
      name: "Thomas Kooij",
      role: "CEO",
      company: "Infra Solutions BV",
      rating: 5,
    },
    {
      quote: "We hebben drie bureaus geprobeerd voor BLURR. Het verschil zit in de proactiviteit — ze bellen jou als er iets beter kan.",
      name: "Sara de Vries",
      role: "E-commerce Manager",
      company: "Modehuis Sara",
      rating: 5,
    },
  ],
} = Astro.props;
---

<section class="ctes-wrap">
  {heading && <h2 class="ctes-heading">{heading}</h2>}
  <div class={`ctes-grid ctes-grid--${layout}`}>
    {testimonials.map(t => (
      <blockquote class="ctes-card">
        {t.rating && (
          <div class="ctes-stars" aria-label={`${t.rating} sterren`}>
            {"★".repeat(t.rating)}{"☆".repeat(5 - t.rating)}
          </div>
        )}
        <p class="ctes-quote">"{t.quote}"</p>
        <footer class="ctes-footer">
          {t.avatar
            ? <img class="ctes-avatar" src={t.avatar} alt={t.name} loading="lazy" />
            : <div class="ctes-avatar-placeholder">{t.name[0]}</div>
          }
          <div>
            <cite class="ctes-name">{t.name}</cite>
            {(t.role || t.company) && (
              <span class="ctes-meta">
                {[t.role, t.company].filter(Boolean).join(", ")}
              </span>
            )}
          </div>
        </footer>
      </blockquote>
    ))}
  </div>
</section>

<style>
  .ctes-wrap { padding: 3rem 0; }
  .ctes-heading {
    font-size: 2rem;
    font-weight: 800;
    color: var(--color-primary, #0a0a0a);
    margin: 0 0 2rem;
  }
  .ctes-grid--grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 1.5rem;
  }
  .ctes-grid--single { max-width: 640px; }
  .ctes-card {
    background: #fff;
    border: 1px solid #ebebeb;
    border-radius: 12px;
    padding: 1.75rem;
    margin: 0;
    transition: box-shadow 0.2s;
  }
  .ctes-card:hover {
    box-shadow: 0 8px 30px rgba(0,0,0,0.07);
  }
  .ctes-stars {
    font-size: 1rem;
    color: #f59e0b;
    letter-spacing: 0.05em;
    margin-bottom: 0.75rem;
  }
  .ctes-quote {
    font-size: 0.975rem;
    line-height: 1.7;
    color: #333;
    margin: 0 0 1.5rem;
    font-style: italic;
  }
  .ctes-footer {
    display: flex;
    align-items: center;
    gap: 0.875rem;
  }
  .ctes-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
  }
  .ctes-avatar-placeholder {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--color-accent, #6366f1);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1rem;
    flex-shrink: 0;
  }
  .ctes-name {
    display: block;
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--color-primary, #0a0a0a);
    font-style: normal;
  }
  .ctes-meta {
    font-size: 0.8rem;
    color: #888;
  }
</style>

Props

Prop Type Default Beschrijving
heading string Sectietitel
testimonials { quote: string; name: string; role?: string; company?: string; avatar?: string; rating?: number }[] Testimonial items
layout 'grid' | 'single' Grid of enkel testimonial

* = verplicht