Skip to main content

Overview

The GoogleReviews component displays customer testimonials and Google reviews in an engaging carousel format, providing powerful social proof for conversion optimization.

GoogleReviews

An advanced carousel component that combines Google reviews and custom testimonials with auto-play, manual navigation, and responsive design.

Props

city
string
default:"Benidorm y España"
The city or region name to display in the section heading. Used for localization and geographic targeting.

Features

Unified Reviews

Combines Google reviews and custom testimonials into a single carousel.

Auto-Play Carousel

Automatic rotation every 8 seconds with pause on hover.

Manual Navigation

Previous/Next buttons for manual control of the carousel.

Responsive Layout

Displays 3 cards on desktop, 2 on tablet, 1 on mobile.

Read More Toggle

Expandable review text with “Leer más” / “Ocultar” functionality.

Trust Indicators

Verified badge and Trustindex certification for credibility.

Usage

---
import GoogleReviews from '@/components/GoogleReviews.astro';
---

<GoogleReviews />

Data Sources

The component merges data from two JSON files:

Google Reviews Data

File: src/data/google-reviews.json
[
  {
    "author": "Juan Pérez",
    "rating": 5,
    "text": "Excelente servicio, muy profesionales.",
    "fullText": "Excelente servicio, muy profesionales. Me ayudaron con el diseño de mi página web y quedó perfecta. Totalmente recomendados.",
    "avatar": "https://lh3.googleusercontent.com/a/...",
    "date": "hace 2 semanas",
    "source": "google"
  }
]

Custom Testimonials Data

File: src/data/testimonios.json
[
  {
    "title": "María González",
    "description": "Increíble trabajo en mi tienda online. Las ventas aumentaron un 150% después del rediseño.",
    "estrellas": 5,
    "image": "/img/testimonials/maria.jpg"
  }
]

Data Mapping

The component transforms testimonials to match the reviews format:
<!-- Location: src/components/GoogleReviews.astro:5-14 -->
const mappedTestimonios = testimonios.map((t) => ({
  author: t.title,
  rating: t.estrellas,
  fullText: t.description,
  text: t.description,
  avatar: t.image,
  date: "Cliente Satisfecho",
  source: "web"
}));

const allReviews = [...mappedTestimonios, ...reviews];

Component Structure

Layout Overview

┌─────────────────────────────────────────────────┐
│               SECTION TITLE                     │
│         Casos de Éxito: Páginas Web...          │
├──────────────┬──────────────────────────────────┤
│              │                                  │
│  EXCELENTE   │  [Review Cards Carousel]         │
│   ★★★★★      │  ← Card 1 | Card 2 | Card 3 →  │
│ 254 reseñas  │                                  │
│   Google     │                                  │
│              │  Verified by: Trustindex         │
└──────────────┴──────────────────────────────────┘

Left Panel - Rating Summary

<!-- Location: src/components/GoogleReviews.astro:39-60 -->
<div class="reviews-left-panel">
  <div class="text-center w-full">
    <div class="rating-label">EXCELENTE</div>
    <div class="rating-stars">★★★★★</div>
    <div class="rating-count">
      A base de <strong>254 reseñas</strong>
    </div>
    <div class="rating-logo">
      <!-- Styled Google logo -->
    </div>
  </div>
</div>
<!-- Location: src/components/GoogleReviews.astro:64-162 -->
<div class="reviews-right-panel">
  <div class="carousel-wrapper">
    <button class="carousel-btn prev" id="prevBtn"></button>
    
    <div class="w-full overflow-hidden" id="carouselContainer">
      <div class="flex" id="carouselTrack">
        {allReviews.map((review) => (
          <div class="review-card">
            <!-- Review card content -->
          </div>
        ))}
      </div>
    </div>
    
    <button class="carousel-btn next" id="nextBtn"></button>
  </div>
</div>

Review Card Structure

<!-- Location: src/components/GoogleReviews.astro:82-148 -->
<div class="review-card">
  <div class="review-card-inner">
    <!-- Header: Avatar, Name, Date -->
    <div class="flex items-center gap-3">
      <img src={review.avatar} alt={review.author} class="w-10 h-10 rounded-full" />
      <div>
        <span class="font-bold">{review.author}</span>
        <p class="text-xs text-gray-500">{review.date}</p>
      </div>
      <div class="text-[#4285F4]">G</div>
    </div>
    
    <!-- Stars and Verified Badge -->
    <div class="flex items-center gap-1.5">
      <div class="flex">★★★★★</div>
      <svg class="w-4 h-4 text-blue-500"><!-- Checkmark --></svg>
    </div>
    
    <!-- Review Text -->
    <p class="review-text">{review.fullText}</p>
    <button class="review-toggle-btn">Leer más</button>
  </div>
</div>

Responsive Cards Display

Shows 3 cards at a time
window.innerWidth >= 1024 ? 3 : ...

Auto-Play System

// Location: src/components/GoogleReviews.astro:232-239
function startAutoPlay() {
  stopAutoPlay();
  autoPlayInterval = setInterval(goToNext, 8000); // 8 seconds
}

function stopAutoPlay() {
  clearInterval(autoPlayInterval);
}
// Location: src/components/GoogleReviews.astro:211-219
function goToNext() {
  const maxIndex = getMaxIndex();
  if (currentIndex < maxIndex) {
    currentIndex++;
  } else {
    currentIndex = 0; // Loop back to start
  }
  updateCarousel();
}

Pause on Hover

// Location: src/components/GoogleReviews.astro:270-274
const carouselWrapper = document.querySelector('.carousel-wrapper');
if (carouselWrapper) {
  carouselWrapper.addEventListener('mouseenter', stopAutoPlay);
  carouselWrapper.addEventListener('mouseleave', startAutoPlay);
}

Expand/Collapse Functionality

Review text is initially truncated to 3 lines with a “Leer más” button:

CSS Truncation

/* Location: src/components/GoogleReviews.astro:392-408 */
.review-text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: 4.5rem;
  line-height: 1.5;
}

.review-text.expanded {
  -webkit-line-clamp: unset;
  max-height: 1000px;
  overflow: visible;
}

Toggle Script

// Location: src/components/GoogleReviews.astro:278-302
carouselContainer.addEventListener('click', (e) => {
  const btn = e.target.closest('.review-toggle-btn');
  if (!btn) return;
  
  const card = btn.closest('.review-card-inner');
  const reviewText = card?.querySelector('.review-text');
  
  const isExpanded = reviewText.classList.contains('expanded');
  if (isExpanded) {
    reviewText.classList.remove('expanded');
    btn.textContent = 'Leer más';
  } else {
    reviewText.classList.add('expanded');
    btn.textContent = 'Ocultar';
  }
});

Styling System

Container Widths

/* Location: src/components/GoogleReviews.astro:306-311 */
.title-wrapper,
.reviews-main-wrapper,
.reviews-badge-wrapper {
  width: 100%;
  max-width: var(--container-width-desktop);
  margin: 0 auto;
}

Panel Layout

/* Location: src/components/GoogleReviews.astro:313-331 */
.reviews-main-wrapper {
  display: flex;
  gap: 30px;
  align-items: center;
}

.reviews-left-panel {
  flex-shrink: 0;
  width: 150px;
}

.reviews-right-panel {
  flex: 1;
  min-width: 0;
  position: relative;
}

Card Styling

/* Location: src/components/GoogleReviews.astro:380-390 */
.review-card-inner {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  background: #f4f4f4;
  border: none;
}

.review-card-inner:hover {
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
}

Google Logo Styling

Multi-color Google logo created with CSS:
<!-- Location: src/components/GoogleReviews.astro:48-57 -->
<span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
  <span style="color: #4285F4;">G</span>
  <span style="color: #EA4335;">o</span>
  <span style="color: #FBBC04;">o</span>
  <span style="color: #4285F4;">g</span>
  <span style="color: #34A853;">l</span>
  <span style="color: #EA4335;">e</span>
</span>

Responsive Design

Mobile Breakpoint (≤1024px)

/* Location: src/components/GoogleReviews.astro:410-426 */
@media (max-width: 1024px) {
  .reviews-main-wrapper {
    flex-direction: column;
    gap: 20px;
  }
  
  .reviews-left-panel {
    width: 100%;
    max-width: 250px;
    margin: 0 auto;
  }
}

Tablet Breakpoint (≤768px)

/* Location: src/components/GoogleReviews.astro:428-443 */
@media (max-width: 768px) {
  .rating-label {
    font-size: 22px;
  }
  
  .rating-stars {
    font-size: 24px;
  }
  
  .reviews-left-panel {
    width: 160px;
  }
}

Trust Elements

Star Rating Display

<div class="rating-stars">★★★★★</div>

Review Count

<div class="rating-count">
  A base de <strong>254 reseñas</strong>
</div>
The review count is hardcoded. You can make it dynamic by using allReviews.length.

Verification Badge

<!-- Location: src/components/GoogleReviews.astro:125-135 -->
<svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
  <path
    fill-rule="evenodd"
    d="M6.267 3.455a3.066 3.066 0 001.745-.723..."
    clip-rule="evenodd"
  />
</svg>

Trustindex Badge

<!-- Location: src/components/GoogleReviews.astro:167-178 -->
<div class="bg-green-700 text-white px-4 py-2 flex items-center gap-2">
  <span>Verificado por: Trustindex</span>
  <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
    <!-- Checkmark icon -->
  </svg>
</div>

Customization Examples

Change Auto-Play Speed

function startAutoPlay() {
  stopAutoPlay();
  autoPlayInterval = setInterval(goToNext, 5000); // 5 seconds instead of 8
}

Dynamic Review Count

<div class="rating-count">
  A base de <strong>{allReviews.length} reseñas</strong>
</div>

Custom Card Layout

For 4 cards on large screens:
function getCardsPerView() {
  return window.innerWidth >= 1400 ? 4 : // Extra large
         window.innerWidth >= 1024 ? 3 : // Desktop
         window.innerWidth >= 768 ? 2 :  // Tablet
         1;                               // Mobile
}

Different Color Scheme

:root {
  --review-bg: #ffffff;
  --review-hover-bg: #f8f9fa;
  --star-color: #fbbf24;
  --verified-color: #4285F4;
}

.review-card-inner {
  background: var(--review-bg);
}

.review-card-inner:hover {
  background: var(--review-hover-bg);
}

Best Practices

Fresh Content

Update reviews regularly to maintain authenticity and show recent customer experiences.

Mix Sources

Combine Google reviews with custom testimonials for variety and comprehensive social proof.

High-Quality Images

Use clear, professional photos for avatar images to build trust.

Authentic Reviews

Use real customer feedback - authenticity is more valuable than perfect reviews.

Moderate Length

Keep reviews between 50-150 words for optimal readability.

Response Rate

Consider showing business responses to reviews to demonstrate engagement.

Performance Optimization

Avatar images use lazy loading:
<img
  src={review.avatar}
  alt={review.author}
  loading="lazy"
  class="w-10 h-10 rounded-full"
/>
Uses CSS transforms for smooth carousel transitions:
.flex {
  transition: transform 0.5s ease-in-out;
}

track.style.transform = `translateX(-${currentIndex * 100}%)`;
Uses event delegation for “Read more” buttons to minimize event listeners:
carouselContainer.addEventListener('click', (e) => {
  const btn = e.target.closest('.review-toggle-btn');
  // Handle click
});

Accessibility

<button
  class="carousel-btn prev"
  aria-label="Reseña anterior"
></button>

<button
  class="carousel-btn next"
  aria-label="Siguiente reseña"
></button>
All images include descriptive alt text:
<img
  src={review.avatar}
  alt={review.author}
/>
  • Previous/Next buttons are keyboard accessible
  • Expand/collapse buttons work with Enter/Space
  • Focus indicators should be styled in CSS
Ensure sufficient color contrast for text:
  • Dark text on light backgrounds
  • Minimum 4.5:1 contrast ratio for body text
  • 3:1 for large text and UI elements

Troubleshooting

Verify:
  • Review data is properly formatted in JSON files
  • CSS Grid/Flexbox is supported by browser
  • getCardsPerView() returns expected values
  • No CSS conflicts with global styles
Ensure:
  • Avatar URLs are valid and accessible
  • CORS headers are set for external images
  • Fallback initial letter display works for missing avatars
  • Estrellas - Star rating display component
  • TrustBadges - Trust indicator badges
  • Autor - Author bio with testimonial

Source Code References

  • GoogleReviews component: src/components/GoogleReviews.astro:1-447
  • Google reviews data: src/data/google-reviews.json
  • Testimonials data: src/data/testimonios.json

Build docs developers (and LLMs) love