How to create a Testimonials Box Component

It's been a while since I wrote something on this blog and I want to apologize for that... emoji-disappointed I should have put more time in it as people requested it. BUT...

I'm back

And I'm here to stay! emoji-smiley

Back to business!

For today I prepared a simple, yet interesting Testimonial Box Component which anyone can use on their personal website to improve visibility for their testimonials from old clients. This will help boost sells and/or get more clients.

Here is a little preview of what we're going to build:

Testimonial Box

Let's start with the simplest part first:

The HTML

For this we'll need a few things:

  1. A container which will hold the entire markup
  2. The testimonial text
  3. The logo and the client details (name and role)
<div class="testimonials-container">
    <p class="testimonial">
        Testimonial text - fill in your own text here
    </p>
    <div class="centered-items">
        <img class="logo" src="add_logo_url" alt="logo" />
        <div class="user-details">
            <h4 class="username">John Doe</h4>
            <p class="role">Blogger</p>
        </div>
    </div>
</div>

As you can see I added a class each tag. This way we can easily target them in the CSS in order to style them. Also, we have a centered-items div. As it's very easy to center align items within a flex container, we're going to create one.

We'll add few more components in the future, but first let's style these... :)

CSS

Now, we're going to style each of these small components individually. Don't worry about the fact that there are multiple CSS declarations, we'll going to break down the ones which aren't that obvious. emoji-wink

@import url('https://fonts.googleapis.com/css?family=Montserrat');

.testimonials-container {
    background-color: #476ce4;
    border-radius: 15px;
    color: #ffffff;
    font-family: 'Montserrat';
    margin: 20px auto;
    max-width: 768px;
    padding: 50px 80px;
}

.testimonial {
    line-height: 28px;
    text-align: justify;
}

.centered-items {
    display: flex;
    align-items: center;
    justify-content: center;
}

.logo {
    border-radius: 50%;
    height: 75px;
    width: 75px;
    object-fit: cover;
}

.user-details {
    margin-left: 10px;
}

.username {
    margin: 0;
}

.role {
    font-weight: normal;
    margin: 2px 0;
}

As you can see, most of the CSS is pretty basic, we have the design or non-structural styling (font, color, background-color) on the container div. This way all the inner tags will inherit the styles and we won't have to declare these styling for all of them.

Next we have some structural styles like padding, margin, height, width, max-width -> these will deal with the alignment of the items within the design. Pretty basic, eh? emoji-stuck_out_tongue

However there are few things I want to note:

  1. In the top, we imported the Google Font - Monserrat which we used to add a nice font to all of our text which is inside the container.
  2. For the testimonials-container div we have max-width and also a margin auto property -> it will center nicely the entire container.
  3. To make the logo image round we used the same width and height on the img tag and also we have a border-radius: 50% -> this will turn the image into a perfect circle. Also, there is a special property: object-fit set to cover -> this will: "fill the height and width of its box, maintaining its aspect ratio but often cropping the image in the process" - perfect for what we need! (Source: CSS-Tricks).

As of now, we have a fully designed Testimonial Box. You could take this and run with it! emoji-smile

Run

But if you decide to stick around, we'll going to add a little bit of JavaScript to it and also some Animations - uuuh! emoji-stuck_out_tongue

JavaScript

You might want to have the option to post multiple testimonials inside your component, right? (who wouldn't?)

This is pretty easy to do. Let's break it down in few simple steps:

  1. Create a JavaScript array which will hold objects with all the changing data (name, position, photo, text)
  2. Add a function which will "inject" the data from above into the HTML markup
  3. Create a setInterval which will update the markup each x miliseconds (in our case - 10000ms which are 10 seconds)
const testimonials = [
    {
        name: 'John Doe',
        position: 'Marketing',
        photo: 'add_photo_url',
        text: 'Testimonial Text'
    }
];

// create a variable for each DOM element we want to target
const testimonialsContainer = document.querySelector('.testimonials-container');
const testimonial = testimonialsContainer.querySelector('.testimonial');
const logo = testimonialsContainer.querySelector('.logo');
const username = testimonialsContainer.querySelector('.username');
const role = testimonialsContainer.querySelector('.role');

// This idx act as a counter
let idx = 1;

function updateTestimonial() {
    // A little bit of desctructuring <img class="emoji-icon" alt="emoji-wink" data-icon="emoji-wink" style="display: inline; margin: 0; margin-top: 1px; position: relative; top: 5px; width: 25px" src="data:image/png;base64, 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" title="emoji-wink" />
    let { name, position, photo, text } = testimonials[idx];

    // Update the DOM elements
    testimonial.innerHTML = text;
    username.innerHTML = name;
    role.innerHTML = position;
    logo.src = photo;

    // Increment the counter
    idx++;

    // Reset the idx when it reaches the end of the array
    if (idx > testimonials.length - 1) {
        idx = 0;
    }
}

// Call the updateTestimonial functions each 10000ms / 10s
setInterval(updateTestimonial, 10000);

In order to be easier for me to explain the code adove, I've added some comments, make sure you read them! :)

You might noticed that we only have one object inside the array. I only added one here as I wanted to spare some space. The code is getting bigger and bigger. emoji-innocent

Something extra, yey! emoji-smile

So far we have the full design implemented and a some JS code where we loops through all the testimonials and update the data in the HTML markup. But... we're not done quite yet. emoji-stuck_out_tongue

Let's make sure that we provide to our visitors a better experience and in order to do that we'll add 3 more things (easy stuff, don't worry!):

  1. A progress bar with animation -> this will provide a nice feedback to the visitors that there is something "loading" and they'll have to wait to read more testimonials. (Otherwise they won't know that there are multiple testimonials).
  2. Adding 2 quote icons from FontAwesome.io to improve the design.
  3. Add some media-queries in order to improve the design on mobile.

Number 1 - HTML

Add the following code snippet right inside the .testimonials-container as it's first child:

<div class="testimonials-container">
    <div class="progress-bar"></div>
    <!-- THE REST OF THE CODE     -->
</div>

CSS

We basically want a div with a fixed heigh (4px in our case) and full width which will have a @keyframes animation that will make it "grow" horizontally (on the X axis) for 10 seconds, and it will be an infinite loop.

.progress-bar {
    background-color: #fff;
    height: 4px;
    width: 100%;
    animation: grow 10s linear infinite;
    transform-origin: left;
}

@keyframes grow {
    0% {
        transform: scaleX(0);
    }
}

You can see that we also added transform-origin: left as it is by default centered and we don't want that in this case.

Note: I used transform inside the keyframes because it animates faster than animating the height (animation stuff, don't worry to much about it now emoji-stuck_out_tongue ).

Number 2 - HTML

Let's add the two new icons we want inside the container. Basically we can add it anywhere in the DOM structure as we'll position it absolute to stay exactly where we want to! (They don't have a choice, ahaha!)

<div class="testimonials-container">
    <i class="fa fa-quote-right fa-quote"></i>
    <i class="fa fa-quote-left fa-quote"></i>
    <!-- THE REST OF THE CODE     -->
</div>

Note: In order for the icons to appear, make sure you import the corresponding FontAwesome CSS via a CDN inside your <head> tag. (You can find the link on their website).

To use the FontAwesome icons it's pretty easy, is just required to add a tag (ussually an i tag) with two classes: fa (tell them that it's a FontAwesome icon) and fa-your-icon (select the icon you want - they provide a list with all the available icons here).

I also added an fa-quote class as I wanted to style it a "little bit". emoji-relaxed

CSS

.testimonials-container {
    /* the other css*/
    position: relative;
}

.fa-quote {
    color: rgba(255, 255, 255, 0.3);
    font-size: 28px;
    position: absolute;
    top: 70px;
}

.fa-quote-right {
    left: 40px;
}

.fa-quote-left {
    right: 40px;
}

We changed the color and the font-size for the icons and we positioned them at a certain location inside the container -> for this we need to add a position: relative to it and position: absolute to the icons. This might be a little confusing at first, but with practice you'll get better at it! (Or I might cover it in more details in a future post, if you want, just let me know!)

And finally...

Number 3 - CSS

For this we'll add the final piece of CSS code to improve the design on mobile devices. This is very important in the "era" we live in as more and more people are browsing the internet from their phones.

@media (max-width: 768px) {
    .testimonials-container {
        padding: 20px 30px;
    }

    .fa-quote {
        display: none;
    }
}

What we're doing here basically is to remove the big padding from the container and hide the icons on mobile, as they will get behind the text and it won't look very good. (You could try to find a better spot to place them, but I've chose to remove them on mobile)

Conclusion

Although it was a pretty simple component to make, it took longer than I expected to explain every tiny-bit of it. Nevertheless I hope it was useful for you and that you learned something new today! emoji-innocent

You can find the code live on Codepen and on Github.

If you have any questions, don't hesitate to contact me. I'll get back to you as soon as I can.

Also, don't forget to follow me on Twitter where I'm sharing all the great (or not, emoji-laughing ) things that I'm building! emoji-wink

Tagged with html5, css3, javascript, animation, keyframes, testimonials, icons, responsive, media-query