Testimonial Carousel HTML CSS JS

Testimonial carousel built with HTML, CSS, and JavaScript. Includes client details, smooth slide transitions, quote styling, and simple navigation controls.

Final output:


Steps for Testimonial Carousel HTML CSS JS:

1. Link the Instrument Sans and Fraunces fonts from Google Fonts in the head.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,100..900;1,9..144,100..900&family=Instrument+Sans:ital,wght@0,400..700;1,400..700&display=swap" rel="stylesheet">

Let’s start with a section element with the class testimonial-section. Inside it, add a .testimonial-wrapper div with a section label span and a .testimonial-carousel div.

<section class="testimonial-section">
    <div class="testimonial-wrapper">
        <span class="section-label">What clients say</span>
        <div class="testimonial-carousel"></div>
    </div>
</section>

Then reset margins, define the Fraunces font as a CSS variable on :root, and set Instrument Sans as the font family. Give the section a near-black background with a warm cream text color.

Use clamp() on the padding so it scales smoothly with the screen size. Center .testimonial-wrapper with the margin inline property set to auto and a max-width so the content doesn’t stretch too wide.

* {
    margin: 0;
    box-sizing: border-box;
}
:root {
    --heading-font: "Fraunces", serif;
}
body {
    font-family: "Instrument Sans", sans-serif;
}
.testimonial-section {
    background-color: hsl(0, 0%, 5%);
    color: hsl(37, 27%, 90%);
    padding: clamp(2rem, 0.72rem + 4.7vw, 5rem) clamp(1.5rem, 0.32rem + 4.5vw, 6rem);
}
.testimonial-section .testimonial-wrapper {
    max-width: 44rem;
    margin-inline: auto;
}
.testimonial-section .section-label {
    font-size: .75rem;
    line-height: 1.3;
    letter-spacing: 1.98px;
    text-transform: uppercase;
}

Output:

2. Add the .testimonial-cards-wrapper div inside .testimonial-carousel. Inside it, add four .testimonial-card divs. The card structure is the same as the one we built in the HTML CSS Testimonial Card #2 post, so each card has a .quotes-icon div and a .testimonial-card-body div with the quote, author image, name, and title. Add the active class to the first card so it shows on load.

<div class="testimonial-carousel">
    <div class="testimonial-cards-wrapper">
        <div class="testimonial-card active">
            <div class="quotes-icon">"</div>
            <div class="testimonial-card-body">
                <p>"The system thinking here is rare..."</p>
                <div class="testimonial-author-details">
                    <img src="mara-solis.png" alt="">
                    <div>
                        <span>Mara Solis</span>
                        <span>CTO - Celeste Systems</span>
                    </div>
                </div>
            </div>
        </div>
        <!-- add more cards -->
    </div>
</div>

Set the position property to relative on .testimonial-cards-wrapper and a fixed height using clamp() so it scales with the screen.

Then stack all cards on top of each other using position absolute with the top and left properties set to 0. Set the opacity property to 0 and the transform property to translateY(5%) on all cards by default.

When the active class is added, set the opacity property to 1 and the transform property back to translateY(0) so the active card fades and slides up into view. Add a transition-delay on the active state so the incoming card waits slightly before animating in, giving the outgoing card time to fade out first.

.testimonial-section .testimonial-carousel {
    margin-top: 3rem;
}
.testimonial-section .testimonial-cards-wrapper {
    position: relative;
    width: 100%;
    height: clamp(20rem, calc(1rem + 35vw), 25rem);
}
.testimonial-section .testimonial-card {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transform: translateY(5%);
    transition: opacity 0.4s ease-in-out, transform .4s ease-in-out;
    background-color: hsl(0, 0%, 5%);
}
.testimonial-section .testimonial-card.active {
    transform: translateY(0);
    opacity: 1;
    transition-delay: 0.3s;
}

Output:

3. The card body styles are also the same as the HTML CSS Testimonial Card #2 post, with two differences. First, the quote font is larger here since it’s a hero element rather than a compact card, so we use clamp() on the font size. Second, the colors are inverted since this section has a dark background instead of a light one.

.testimonial-section .quotes-icon {
    margin-top: 1.75rem;
    font-size: 5rem;
    line-height: 1;
    font-family: var(--heading-font);
}
.testimonial-section .testimonial-card-body {
    margin-top: -1rem;
}
.testimonial-section .testimonial-card-body p {
    font-family: var(--heading-font);
    font-weight: 300;
    font-style: italic;
    font-size: clamp(1.25rem, calc(1rem + 1.11vw), 2rem);
    line-height: 1.4;
    letter-spacing: -0.32px;
    margin-bottom: 1.5rem;
    color: hsl(37, 27%, 90%);
}
.testimonial-section .testimonial-author-details {
    display: flex;
    gap: .875rem;
    padding-top: 2rem;
    border-top: 1px solid hsla(0, 0%, 0%, 0.09);
}
.testimonial-section .testimonial-author-details img {
    width: 2.875rem;
    height: 2.875rem;
    border-radius: 50%;
}
.testimonial-section .testimonial-author-details div {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .25rem;
}
.testimonial-section .testimonial-author-details span:first-child {
    font-weight: 600;
    font-size: .875rem;
    line-height: 1.4;
    letter-spacing: -0.14px;
}
.testimonial-section .testimonial-author-details span:last-child {
    font-size: .875rem;
    line-height: 1.4;
    color: hsl(0, 0%, 43%);
}

Output:

4. Add the .controls div after .testimonial-cards-wrapper. Inside it, add .controls-left with the progress bar, and .controls-right with the prev and next buttons.

<div class="controls">
    <div class="controls-left">
        <div class="bar-background">
            <div class="bar-fill" id="bar-fill"></div>
        </div>
    </div>
    <div class="controls-right">
        <button id="prev-btn">
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M9 2L4 7L9 12" stroke="#555555" stroke-width="1.5" stroke-linecap="round"
                    stroke-linejoin="round" />
            </svg>
        </button>
        <button id="next-btn">
            <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M5 2L10 7L5 12" stroke="#555555" stroke-width="1.5" stroke-linecap="round"
                    stroke-linejoin="round" />
            </svg>
        </button>
    </div>
</div>

Use flexbox with the justify content property set to space-between on .controls to push the progress bar and buttons to opposite ends. Style .bar-background as a thin horizontal line with a semi-transparent white color.

Style .bar-fill as the active portion that grows as you move through the cards, with a transition on the width property so it animates smoothly. And style the buttons as circular transparent elements with a subtle border.

.testimonial-section .controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.25rem;
}
.testimonial-section .controls-left {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-basis: 9.125rem;
}
.testimonial-section .bar-background {
    flex-basis: 6rem;
    height: 1px;
    background: hsla(0, 0%, 100%, .5);
}
.testimonial-section .bar-fill {
    height: 100%;
    background: hsl(0, 0%, 100%);
    width: 33%;
    transition: width 0.3s;
}
.testimonial-section .controls-right {
    display: flex;
    align-items: center;
    gap: .5rem;
}
.testimonial-section .controls-right button {
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid hsla(37, 27%, 90%, .5);
    background-color: transparent;
    cursor: pointer;
    border-radius: 50%;
}
.testimonial-section .controls-right button:hover {
    background-color: hsla(37, 27%, 90%, .1);
}

Output:

5. Now add the JavaScript. Link script.js before the closing body tag. The logic here works exactly the same way as the image carousel we built in the About Section HTML CSS JS with Image Carousel post. The only differences are that we’re targeting .testimonial-card elements instead of .slide, and there’s no caption to update since this carousel doesn’t have one.

document.addEventListener('DOMContentLoaded', function () {
    var slides = document.querySelectorAll('.testimonial-section .testimonial-card');
    var currentIndex = 0;
    var barFillEl = document.querySelector('.testimonial-section #bar-fill');

    function updateSlide() {
        for (var i = 0; i < slides.length; i++) {
            slides[i].classList.remove('active');
        }
        slides[currentIndex].classList.add('active');
        var percent = ((currentIndex + 1) / slides.length) * 100;
        barFillEl.style.width = percent + "%";
    }

    document.getElementById('next-btn').addEventListener('click', function () {
        currentIndex = currentIndex + 1;
        if (currentIndex > slides.length - 1) {
            currentIndex = 0;
        }
        updateSlide();
    });

    document.getElementById('prev-btn').addEventListener('click', function () {
        currentIndex = currentIndex - 1;
        if (currentIndex < 0) {
            currentIndex = slides.length - 1;
        }
        updateSlide();
    });
});

Output:


Final Output Code for Testimonial Carousel HTML CSS JS:

HTML

<section class="testimonial-section">
    <div class="testimonial-wrapper">
        <span class="section-label">What clients say</span>
        <div class="testimonial-carousel">
            <div class="testimonial-cards-wrapper">
                <div class="testimonial-card active">
                    <div class="quotes-icon">“</div>
                    <div class="testimonial-card-body">
                        <p>“The system thinking here is rare. Not just a visual layer, a coherent language that our
                            engineers could build from without constant back-and-forth. Saved us weeks of
                            interpretation
                            overhead.”</p>
                        <div class="testimonial-author-details">
                            <img src="Mara-Solis.png" alt="">
                            <div>
                                <span>Mara Solis</span>
                                <span>CTO - Celeste Systems</span>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="testimonial-card">
                    <div class="quotes-icon">“</div>
                    <div class="testimonial-card-body">
                        <p>“The work was surgical. No wasted motion, no ambiguity, every decision traceable back to
                            a clear principle. We shipped six weeks ahead of schedule and the team stopped asking
                            questions mid-sprint.”
                        </p>
                        <div class="testimonial-author-details">
                            <img src="Miriam-Voss.png" alt="">
                            <div>
                                <span>Miriam Voss</span>
                                <span>Head of Product - Forma Studio</span>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="testimonial-card">
                    <div class="quotes-icon">“</div>
                    <div class="testimonial-card-body">
                        <p>
                            “I've worked with a lot of designers. Most show you what you asked for. This was the
                            first time someone showed me what I actually needed, and then defended it until I
                            understood why.”
                        </p>
                        <div class="testimonial-author-details">
                            <img src="Tobias-Ren.png" alt="">
                            <div>
                                <span>Tobias Ren</span>
                                <span>Founder - Meridian Labs</span>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="testimonial-card">
                    <div class="quotes-icon">“</div>
                    <div class="testimonial-card-body">
                        <p>“Our conversion rate went up 34% in the first month after launch. I'd love to attribute
                            that to something else, but I can't. The clarity of the new interface removed every
                            hesitation from the checkout path.”</p>
                        <div class="testimonial-author-details">
                            <img src="Elena-Marsh.png" alt="">
                            <div>
                                <span>Elena Marsh</span>
                                <span>VP Growth - Lattice Finance</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="controls">
                <div class="controls-left">
                    <div class="bar-background">
                        <div class="bar-fill" id="bar-fill"></div>
                    </div>
                </div>
                <div class="controls-right">
                    <button id="prev-btn">
                        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
                            <path d="M9 2L4 7L9 12" stroke="#555555" stroke-width="1.5" stroke-linecap="round"
                                stroke-linejoin="round" />
                        </svg>
                    </button>
                    <button id="next-btn">
                        <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
                            <path d="M5 2L10 7L5 12" stroke="#555555" stroke-width="1.5" stroke-linecap="round"
                                stroke-linejoin="round" />
                        </svg>
                    </button>
                </div>
            </div>
        </div>
    </div>
</section>

CSS

* {
    margin: 0;
    box-sizing: border-box;
}
:root {
    --heading-font: "Fraunces", serif;
}
body {
    font-family: "Instrument Sans", sans-serif;
}
.testimonial-section {
    background-color: hsl(0, 0%, 5%);
    color: hsl(37, 27%, 90%);
    padding: clamp(2rem, 0.72rem + 4.7vw, 5rem) clamp(1.5rem, 0.32rem + 4.5vw, 6rem);
}
.testimonial-section .testimonial-wrapper {
    max-width: 44rem;
    margin-inline: auto;
}
.testimonial-section .section-label {
    font-size: .75rem;
    line-height: 1.3;
    letter-spacing: 1.98px;
    text-transform: uppercase;
}
.testimonial-section .testimonial-carousel {
    margin-top: 3rem;
}
.testimonial-section .testimonial-cards-wrapper {
    position: relative;
    width: 100%;
    height: clamp(20rem, calc(1rem + 35vw), 25rem);
}
.testimonial-section .testimonial-card {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transform: translateY(5%);
    transition: opacity 0.4s ease-in-out, transform .4s ease-in-out;
    background-color: hsl(0, 0%, 5%);
}
.testimonial-section .testimonial-card.active {
    transform: translateY(0);
    opacity: 1;
    transition-delay: 0.3s;
}
.testimonial-section .quotes-icon {
    margin-top: 1.75rem;
    font-size: 5rem;
    line-height: 1;
    font-family: var(--heading-font);
}
.testimonial-section .testimonial-card-body {
    margin-top: -1rem;
}
.testimonial-section .testimonial-card-body p {
    font-family: var(--heading-font);
    font-weight: 300;
    font-style: italic;
    font-size: clamp(1.25rem, calc(1rem + 1.11vw), 2rem);
    line-height: 1.4;
    letter-spacing: -0.32px;
    margin-bottom: 1.5rem;
    color: hsl(37, 27%, 90%);
}
.testimonial-section .testimonial-author-details {
    display: flex;
    gap: .875rem;
    padding-top: 2rem;
    border-top: 1px solid hsla(0, 0%, 0%, 0.09);
}
.testimonial-section .testimonial-author-details img {
    width: 2.875rem;
    height: 2.875rem;
    border-radius: 50%;
}
.testimonial-section .testimonial-author-details div {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: .25rem;
}
.testimonial-section .testimonial-author-details span:first-child {
    font-weight: 600;
    font-size: .875rem;
    line-height: 1.4;
    letter-spacing: -0.14px;
}
.testimonial-section .testimonial-author-details span:last-child {
    font-size: .875rem;
    line-height: 1.4;
    color: hsl(0, 0%, 43%);
}
.testimonial-section .controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.25rem;
}
.testimonial-section .controls-left {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-basis: 9.125rem;
}
.testimonial-section .bar-background {
    flex-basis: 6rem;
    height: 1px;
    background: hsla(0, 0%, 100%, .5);
}
.testimonial-section .bar-fill {
    height: 100%;
    background: hsl(0, 0%, 100%);
    width: 33%;
    transition: width 0.3s;
}
.testimonial-section .controls-right {
    display: flex;
    align-items: center;
    gap: .5rem;
}
.testimonial-section .controls-right button {
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid hsla(37, 27%, 90%, .5);
    background-color: transparent;
    cursor: pointer;
    border-radius: 50%;
}
.testimonial-section .controls-right button:hover {
    background-color: hsla(37, 27%, 90%, .1);
}

JS:

document.addEventListener('DOMContentLoaded', function () {
    var slides = document.querySelectorAll('.testimonial-section .testimonial-card');
    var currentIndex = 0;
    var barFillEl = document.querySelector('.testimonial-section #bar-fill');

    function updateSlide() {
        for (var i = 0; i < slides.length; i++) {
            slides[i].classList.remove('active');
        }
        slides[currentIndex].classList.add('active');
        var percent = ((currentIndex + 1) / slides.length) * 100;
        barFillEl.style.width = percent + "%";
    }

    document.getElementById('next-btn').addEventListener('click', function () {
        currentIndex = currentIndex + 1;
        if (currentIndex > slides.length - 1) {
            currentIndex = 0;
        }
        updateSlide();
    });

    document.getElementById('prev-btn').addEventListener('click', function () {
        currentIndex = currentIndex - 1;
        if (currentIndex < 0) {
            currentIndex = slides.length - 1;
        }
        updateSlide();
    });
});

If you have any doubts or stuck somewhere, you can reach out through Coding Yaar's Discord server.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Most Voted
Newest Oldest
0
Would love your thoughts, please comment.x
()
x