Recreating a testimonial section with Tailwind CSS and JavaScript that we did with Alpine.js in the previous tutorial.
Testimonials are a great way to showcase your work and build trust with potential clients. They can be used in a variety of contexts, such as a landing page, a blog post, or a product page. Testimonials can be written by your clients or by your team, and can be in the form of a quote, a video, or a written review.
Use cases:
And many other reasons to use testimonials.
Understanding the code:
style="display: none;"
: This is the CSS that hides the testimonial text initially.lass="testimonial ..."
: This is the custom CSS class that styles the testimonial text.Classes are removed for brevity, but I’ll keep those classes relevant to the tutorial.
<!-- Dynamically render testimonials -->
{
testimonials.map((testimonial, index) => (
<div
class="testimonial ..."
data-index={index + 1}
style="display: none;">
<p>{testimonial.content}</p>
</div>
))
}
class="avatar-button ..."
: This is the custom CSS class that styles the button.
Classes are removed for brevity, but I’ll keep those classes relevant to the tutorial.<!-- Dynamically render name and role -->
{
testimonials.map((testimonial, index) => (
<button
class="avatar-button ..."
data-index={index + 1}>
<img
src={testimonial.imageUrl}
alt="#_"
/>
</button>
))
}
class="testimonial-info ..."
: This is the custom CSS class that styles the testimonial info.style="display: none;"
: This is the CSS that hides the testimonial info initially.Classes are removed for brevity, but I’ll keep those classes relevant to the tutorial.
<!-- Dynamically render name and role -->
{
testimonials.map((testimonial, index) => (
<div
class="testimonial-info ..."
data-index={index + 1}
style="display: none;">
<h2>{testimonial.name}</h2>
<a href="#">{testimonial.title}</a>
</div>
))
}
<div class="mt-6 border-t pt-12 max-w-xl mx-auto w-full">
<!-- Dynamically render testimonials -->
{
testimonials.map((testimonial, index) => (
<div
class="testimonial pb-6 text-neutral-500 font-medium mx-auto max-w-2xl h-32 text-balance items-center text-center"
data-index={index + 1}
style="display: none;">
<p>{testimonial.content}</p>
</div>
))
}
<div class="flex items-center justify-center mt-12">
<!-- Buttons to change the active testimonial -->
{
testimonials.map((testimonial, index) => (
<button
class="avatar-button inline-block mx-2 font-bold text-center rounded-full focus:outline-none focus:ring-2 ring-offset-4 ring-offset-white ring-orange-600 size-12"
data-index={index + 1}>
<img
class="inline-block size-12 rounded-full object-cover"
src={testimonial.imageUrl}
alt="#_"
/>
</button>
))
}
</div>
<!-- Dynamically render name and role -->
{
testimonials.map((testimonial, index) => (
<div
class="testimonial-info text-center py-6"
data-index={index + 1}
style="display: none;">
<h2 class="text-black font-medium text-base">{testimonial.name}</h2>
<a
href="#"
class="text-xs text-orange-500">
{testimonial.title}
</a>
</div>
))
}
</div>
document.addEventListener("DOMContentLoaded", () => {
: This is the event listener that will be added to the document object.const testimonials = document.querySelectorAll(".testimonial");
: This is the code that will select all the testimonial elements.const testimonialInfos = document.querySelectorAll(".testimonial-info");
: This is the code that will select all the testimonial info elements.const avatarButtons = document.querySelectorAll(".avatar-button");
: This is the code that will select all the avatar buttons.const showTestimonial = (index) => {
: This is the function that will be called when a button is clicked.testimonials.forEach((testimonial) => {
: This is the code that will iterate over each testimonial element.testimonial.dataset.index == index ? "block" : "none";
: This is the code that will change the display property of the testimonial element based on the index of the button that was clicked.testimonialInfos.forEach((info) => {
: This is the code that will iterate over each testimonial info element.info.style.display = info.dataset.index == index ? "block" : "none";
: This is the code that will change the display property of the testimonial info element based on the index of the button that was clicked.avatarButtons.forEach((button) => {
: This is the code that will iterate over each avatar button element.button.addEventListener("click", (e) => {
: This is the event listener that will be added to each avatar button element.e.preventDefault();
: This is the code that will prevent the default behavior of the button, which is to navigate to the link.const index = button.dataset.index;
: This is the code that will get the index of the button that was clicked.showTestimonial(index);
: This is the code that will call the showTestimonial
function with the index of the button that was clicked.showTestimonial(1);
: This is the code that will call the showTestimonial
function with the index of the first testimonial.document.addEventListener("DOMContentLoaded", () => {
const testimonials = document.querySelectorAll(".testimonial");
const testimonialInfos = document.querySelectorAll(".testimonial-info");
const avatarButtons = document.querySelectorAll(".avatar-button");
const showTestimonial = (index) => {
testimonials.forEach((testimonial) => {
testimonial.style.display =
testimonial.dataset.index == index ? "block" : "none";
});
testimonialInfos.forEach((info) => {
info.style.display = info.dataset.index == index ? "block" : "none";
});
};
avatarButtons.forEach((button) => {
button.addEventListener("click", (e) => {
e.preventDefault();
const index = button.dataset.index;
showTestimonial(index);
});
});
showTestimonial(1);
});
This is a simple testimonial section that can be used for any type of content, such as products, services, or blog posts or anything else where you need to prove your value. But before using it make sure to make it fully accessible.
Hope you enjoyed this tutorial and have a great day!
/Michael Andreuzza
Get lifetime access to every theme available today for $199 $139 and own
them forever. Plus, new themes, lifetime updates, use on unlimited
projects and enjoy lifetime support.
— No subscription required!