How to build a Bootstrap testimonial slider
It rests on the carousel component built into Bootstrap, plus the framework’s grid, card and utility classes for layout. Everything here targets Bootstrap 5.3.8, the current release in the official docs as of October 2026.
- Add the viewport meta tag, the CSS link in the head and the JavaScript before the closing body tag. The jsDelivr links in the Bootstrap docs include integrity hashes, so copy them along with the version number.
- Create the wrapper: one div with the classes carousel and slide, and a unique id.
- Put a carousel-inner div inside it, then one carousel-item per testimonial. The first item needs the active class, or the slider stays invisible (Bootstrap docs).
- Fill each slide with a figure, a blockquote and a figcaption, plus the avatar and rating.
- Wire up the controls and indicators. Every data-bs-target must match the wrapper id.
- Turn on autoplay last, once the manual version works.
Either the bundle or the standalone JavaScript file will do. Popper is only needed for dropdowns, popovers and tooltips, so the carousel doesn’t care which one you load.
Slide markup for one testimonial
Attribution goes outside the blockquote. Bootstrap’s typography docs say the HTML spec requires it, so the quote sits in a figure with a figcaption and the source name wrapped in cite.
<div id="testimonialSlider" class="carousel slide">
<div class="carousel-indicators">
<button type="button" data-bs-target="#testimonialSlider" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Testimonial 1"></button>
<button type="button" data-bs-target="#testimonialSlider" data-bs-slide-to="1" aria-label="Testimonial 2"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<figure class="text-center px-5 pb-5 mb-0">
<img src="avatar-1.jpg" class="rounded-circle mb-3" width="64" height="64" alt="Portrait of Customer Name">
<blockquote class="blockquote">
<p>Short, specific quote from the customer.</p>
</blockquote>
<figcaption class="blockquote-footer">
Customer Name, <cite title="Company">Job title at Company</cite>
</figcaption>
</figure>
</div>
<div class="carousel-item">
<!-- second testimonial, same structure, no active class -->
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#testimonialSlider" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#testimonialSlider" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
The px-5 and pb-5 padding keeps the quote clear of the arrows and the indicator dots.
Controls and indicators
Controls are button elements with data-bs-slide set to prev or next. The docs recommend buttons, though a link with role=”button” also works.
Indicators use data-bs-slide-to with a zero-based index, and the starting dot carries active and aria-current=”true”. Every data-bs-target has to match the wrapper id. That only starts to matter once a second slider lands on the same page, but it’s easier to get right from the beginning. The visually-hidden text names each arrow for screen readers, and the icon spans stay aria-hidden.
The default arrows are white, so they disappear on a light background. Set data-bs-theme=”dark” on the wrapper, because .carousel-dark has been deprecated since Bootstrap 5.3.0.
Each arrow zone takes 15% of the slider width by default ($carousel-control-width in the Sass variables). On a narrow slider that puts the arrows over the text unless the slide has side padding.
Star ratings and avatars
Stars are only decoration, so draw them with Bootstrap Icons, the Bootstrap team’s separate icon set, and mark each icon aria-hidden. Add a visually-hidden line such as “Rated 5 out of 5” next to them so the rating survives without the visuals.
Avatars should use rounded-circle with explicit width and height, so slides don’t shift when the image loads. The alt text is the customer’s name, not “avatar”.
Autoplay, timing and fade settings
Autoplay starts with data-bs-ride=”carousel”, cycles every 5000 ms by default and pauses while the pointer sits over the slider (Bootstrap docs, v5.3.8).
The default pause is “hover”, which stops on mouseenter and resumes on mouseleave. Ride defaults to false, so a slider without data-bs-ride never autoplays and only changes when the visitor acts. The transition itself takes 0.6 s, set by $carousel-transition-duration.
Autoplay and interval
The ride option decides who starts the motion. With “carousel” the slider autoplays on page load, while true makes it wait until the visitor changes a slide once.
A slider with data-bs-ride=”carousel” initializes itself, and creating it again with new bootstrap.Carousel() is a mistake the docs warn against. Without that attribute you get the opposite situation. The docs say carousels must be initialized manually, and the swipe listeners are not registered until the visitor first uses a control or indicator.
Quote length varies, so one timer rarely fits every slide. Put data-bs-interval on a carousel-item to give a long quote more time, for example 9000 for a 60-word testimonial.
Read the quote aloud once and add a few seconds. That’s an editor’s rule of thumb, not a documented value.
Pause, touch and wrap
With pause set to hover, a touch device pauses on touchend and waits two intervals before resuming. Swipe is on by default once the carousel is initialized (autoplaying carousels initialize themselves; a manual one needs new bootstrap.Carousel(‘#testimonialSlider’)), and data-bs-touch=”false” turns it off.
Wrap set to true loops forever, and false stops at the last slide. The carousel reacts to keyboard events unless keyboard is false, and cycling stops whenever the page is not visible, through the Page Visibility API.
Apart from the hidden-page stop, hover and the touchend pause on touch screens are the only automatic pause triggers Bootstrap documents. Keyboard and screen reader users get nothing from it, so the accessibility settings below matter.
Slide versus fade
Slide is the default. Adding carousel-fade swaps it for a crossfade.
Slide gives a direction cue that matches the previous and next arrows, and its speed comes from $carousel-transition-duration. Fade is calmer for text-only quotes and reads as less abrupt when the quotes differ in length. For plain text I’d pick fade, though text-only items may need .bg-body or a custom background to crossfade properly (Bootstrap docs).
Change the speed in Sass before compiling, or override the CSS if you use the compiled file. When a rule combines several transitions, define transform first.
How do you show several testimonials per slide?
Nest a row of cards inside each carousel-item. The carousel options (interval, keyboard, pause, ride, touch, wrap) include no items-per-view setting, so the layout comes from the grid and not from the plugin.
A row with row-cols-1 and row-cols-md-3 gives one card per row on phones and three across from the md breakpoint up. That split comes from the grid system underneath Bootstrap, and each slide holds one such row.
Keeping card heights equal
Carousels do not normalize slide dimensions (Bootstrap docs). Testimonials of different lengths make the slider jump in height on every change.
Build each quote as one of the cards built with Bootstrap and add h-100. Setting $card-height to 100% in Sass makes equal height the default for every card.
What breaks on small screens
Below md, row-cols-1 stacks the three cards of one slide on top of each other. The slide grows to three times its height.
Hiding cards two and three with d-none d-md-block fixes the height. It also removes those testimonials from phones entirely, because they only exist inside that slide.
The clean fix is one carousel-item per testimonial on small screens. That means a second markup set shown below md with display utilities, or a script that steps single cards.
Grid, extension or separate library
| Approach | Extra code | Movement per click | Main limit |
|---|
| Grid inside each carousel-item | None | One full row | Phones stack or hide cards |
| Bootstrap 5 multi-item extension | JS and CSS add-on | Varies by plugin | Depends on a third-party plugin |
| Swiper or Splide | Separate library | Configurable | A second slider API next to Bootstrap’s |
Swiper’s API documentation lists slidesPerView and slidesPerGroup, along with a breakpoints parameter. Splide’s options guide covers perPage and perMove plus breakpoints, and its GitHub README says Splide is written in TypeScript with no dependencies.
A handful of short quotes fits the grid-in-slide approach. If you need looping or dragging, or want one card to step at a time, move to Swiper or Splide instead of patching the carousel.
Check what a multi-item snippet depends on before copying it. MDB’s support forum describes the multi-item carousel in its own kit as a premium feature, so a snippet labeled Bootstrap may not run on plain Bootstrap.
What accessibility work does a Bootstrap testimonial slider need?
Bootstrap covers hover pause, button controls with hidden labels, and reduced-motion handling. You still have to add a pause button, a stop on focus, live-region handling and the carousel roles.
| Behavior | Bootstrap 5.3.8 | W3C carousel pattern | What you add |
|---|
| Pause on hover | Built in | Required | Nothing |
| Stop on keyboard focus | Not described in the docs | Required, no automatic restart | A focusin listener that pauses |
| Pause and start button | Not built in, recommended by the docs | Required, first in tab order | A button that toggles pause and cycle |
| Live region on the slide wrapper | Not described | Optional: off while rotating, polite when not | Toggle the attribute with the button |
| Container and slide roles | Not described | Role group or region, plus roledescription | aria-roledescription and labels |
Pause control and focus
A moving testimonial slider is subject to the usual web accessibility rules for moving content. WCAG 2.2 success criterion 2.2.2 (Level A) requires a pause, stop or hide mechanism for content that starts automatically, lasts more than five seconds and sits beside other content.
The W3C’s Understanding document (updated August 2026) says that stopping only while focus sits on the element does not count as a mechanism. Hover pause has the same shape, because it releases the moment the pointer leaves.
The ARIA Authoring Practices pattern differs in one detail. After focus enters the carousel, rotation stays stopped until the user restarts it. It also puts the rotation button first in the tab sequence and changes its label, in the APG example, between “Stop automatic slide show” and “Start automatic slide show”.
Bootstrap’s pause and cycle methods cover the button. Test a manual pause with the pointer moving out of the slider, since the documented hover behavior resumes cycling on mouseleave.
Labels, roles and live regions
Bootstrap’s indicator buttons already carry aria-label and aria-current. The rest of the pattern relies on ARIA roles that you write yourself.
The container gets role region or group, aria-roledescription=”carousel” and a label. Each slide gets role group and aria-roledescription=”slide”, labeled “3 of 10” when no unique name exists. The slide wrapper takes aria-live off while rotating and polite when it is not.
Reduced motion and contrast
Bootstrap disables the sliding animation in carousels when the visitor sets prefers-reduced-motion (Bootstrap accessibility docs). Those visitors get an instant swap, but the docs do not say the autoplay timer stops, so the pause button still matters.
The same docs warn that some default palette colors fall below the WCAG 2.2 text contrast minimum on light backgrounds. Test the quote text color against the slide background before shipping.
Bootstrap 4 vs Bootstrap 5 testimonial sliders
Build on Bootstrap 5. Bootstrap 4 reached end of life on January 1, 2023, according to the release schedule in the project’s twbs/release repository, and the v4.6 docs now open with an end-of-life banner.
The two versions also differ in markup, which is where copied testimonial snippets fail.
| Part of the slider | Bootstrap 4.6 | Bootstrap 5.3 |
|---|
| JavaScript | jQuery plugin, $('.carousel').carousel() | Vanilla JavaScript, jQuery optional |
| Autoplay and options | data-ride, data-interval | data-bs-ride, data-bs-interval |
| Controls and indicators | data-target, data-slide, data-slide-to | data-bs-target, data-bs-slide, data-bs-slide-to |
| Indicator markup | ol with li elements | div with button elements |
| Hidden label class | sr-only | visually-hidden |
Bootstrap 5 does not need jQuery, though it still works with it. If jQuery sits on the page, Bootstrap registers its plugins there automatically (Bootstrap 5.3 docs).
What breaks when you paste a Bootstrap 4 snippet
Many testimonial templates still target Bootstrap 4, and the failures are quiet. The slider renders, then misbehaves.
- The v4 data attributes do nothing in v5, which reads only the data-bs- names, so controls and autoplay both die.
- The sr-only class was renamed, so “Previous” and “Next” appear as visible text.
- The .media component was dropped, which means avatar-plus-text layouts need flex utilities now.
- .card-deck is gone in favor of the grid.
- ml-* and mr-* became ms-* and me-*, and text-left became text-start.
A quick version check: if data-ride or data-target shows up anywhere in the markup, the snippet was written for Bootstrap 4.
When Bootstrap 4 is still the constraint
Bootstrap 5.0.0 dropped Internet Explorer 10 and 11 (Bootstrap migration docs). A site that still has to serve those browsers is stuck on Bootstrap 4, and that is a question of cross-browser compatibility, not of carousel features.
Teams that cannot migrate are pointed to HeroDevs’ Never-Ending Support program by that end-of-life banner in the Bootstrap 4.6 docs.
Why is the Bootstrap testimonial slider not working?
A slider that looks right and does nothing usually has one of a few easy-to-miss causes: Bootstrap 4 attributes on a Bootstrap 5 page, event listeners that seem silent when jQuery is on the page, calls fired mid-transition, or a slider sitting inside a hidden container.
Version mismatch and missing JavaScript
A Bootstrap 4 snippet on a Bootstrap 5 page shows the first testimonial and then sits still. The arrows do nothing and autoplay never starts. Previous and Next show up as plain text.
A missing JavaScript file produces the same freeze. The carousel is on Bootstrap’s list of components that require JavaScript, and the plugins have no special fallback when it is off (Bootstrap 5.3 docs).
Events that seem not to fire
The carousel docs tell you to listen for slid.bs.carousel with addEventListener. The JavaScript docs say that if jQuery is on the window and the body has no data-bs-no-jquery attribute, Bootstrap emits its events through jQuery’s event system, so listeners use .on().
Both pages describe the same plugin, and they disagree on the listener. In the 5.3.8 source, Bootstrap fires the jQuery event first and then still dispatches the native event, unless a jQuery handler calls stopImmediatePropagation(), so a plain addEventListener on the carousel element normally works. If a handler stays silent, look for a jQuery handler that stops the event, test the handler on your own page, and add data-bs-no-jquery to the body if you want the native version to win.
Calls ignored mid-transition
Programmatic calls are asynchronous, and a call on a component that is still transitioning is ignored (Bootstrap docs). In the 5.3.8 source that holds for next() and prev(), so custom buttons that fire them twice in a row lose the second step. to() is the exception: it waits for the running slide to finish, and the example below chains the jumps through slid.bs.carousel the way the docs do.
const el = document.querySelector('#testimonialSlider')
const carousel = bootstrap.Carousel.getOrCreateInstance(el)
el.addEventListener('slid.bs.carousel', () => carousel.to(2), { once: true })
carousel.to(1) // the jump to 2 waits for this transition to finish
Hidden containers, nesting and frameworks
A slider inside a collapsed accordion, tab or modal should advance with nextWhenVisible, which skips the move when the page, the carousel or its parent is not visible.
Nested carousels are not supported, so a testimonial slider inside another slide breaks.
Bootstrap’s JavaScript is not fully compatible with React, Vue and Angular, because both Bootstrap and the framework may mutate the same DOM element. Use the framework-specific package, such as React Bootstrap.
When is a testimonial slider the wrong choice?
A slider is the wrong choice when the quotes matter more than the space they save. Bootstrap’s own docs recommend avoiding autoplay and warn that carousels in general can cause usability and accessibility challenges.
The Bootstrap 4.6 docs went further and called carousels generally not compliant with accessibility standards. The 5.3 docs use softer wording.
What the usability research found
The usability findings on carousels have stayed consistent for years, and Nielsen Norman Group (NN/g) reviewed its carousel guidance again in August 2026.
Users ignore animated items that look like ads, and NN/g’s eyetracking found animated ads get looked at 27% of the time. Its advice is five or fewer frames, because people struggle to recognize what they have already seen beyond that. For reading time the guideline is 3 words per second.
That guideline puts a 60-word quote at 20 seconds. It is more than double the 9000 ms example used in the autoplay settings above, so use the longer figure.
In a usability study that NN/g wrote up in 2013, a participant could not find a prominent £100-off appliance offer on the Siemens UK homepage because the panel auto-rotated every five seconds. She gave up and assumed Siemens had no special deals.
A static grid as the alternative
Every quote visible at once beats a rotation nobody waits for. The cards from the multi-testimonial layout work without the carousel wrapper, with no controls and no timer.
On phones, NN/g advises against auto-forwarding. Pages are short, readers scroll fast, and by the time a slide changes they have often moved past the above-the-fold area.
NN/g also recommends repeating anything important from a carousel elsewhere in the page. For testimonials, that means the strongest quote as static text.
When a slider still earns its place
It earns a spot in a fixed-height band or sidebar where a grid will not fit, with five short quotes or fewer, manual controls only, and the best quote also shown as plain text.
Bootstrap testimonial slider FAQ
How do you swap the default arrows for custom icons?
Edit the icon, not the button. Keep the button element, its data-bs-slide attribute and the visually-hidden label, and change only the span inside it.
To restyle every slider at once, override $carousel-control-prev-icon-bg and $carousel-control-next-icon-bg in Sass.
Can the slider be controlled with JavaScript?
Yes. Fetch the instance with bootstrap.Carousel.getOrCreateInstance, then call cycle, pause, prev, next or to(index), where the index starts at 0.
The slide.bs.carousel and slid.bs.carousel events fire on the carousel element and report direction, relatedTarget, from and to (Bootstrap 5.3 docs).
Is there a ready-made package for a Bootstrap testimonial slider?
Yes. cdnjs lists a bootstrap-testimonial-slider library at version 1.0.0, described as a combination of the Bootstrap carousel and testimonials, with light, dark and right-to-left stylesheets.
The listing shows only CSS files, so the slide behavior still comes from Bootstrap’s own JavaScript.
How many testimonials should one slider hold, and how long can each quote be?
Five or fewer. Nielsen Norman Group’s carousel guidance (reviewed August 2026) sets that ceiling because people struggle to keep track of more frames.
Keep each quote short enough to read at about 3 words per second within its interval.
Which setup to ship first
Ship the Bootstrap testimonial slider with manual controls and no autoplay, then add motion only after testing it. That order matters because each step removes a failure the next one would hide.
Start with Bootstrap 5.3 markup and manual controls, initialized with new bootstrap.Carousel() so swipe works from the first visit. Then check keyboard, touch and reduced-motion behavior. Autoplay comes last, and only with a pause button.
Timing explains why autoplay goes last. Five 60-word quotes at 3 words per second take 100 seconds for one full rotation, so a visitor who leaves sooner never sees the whole set.
The downside is exposure. Quotes after the first get fewer views, because visitors must click to reach them.
As of October 29, 2026, Bootstrap 6.0.0-alpha.1 (released October 8, 2026) is the only v6 release, and 5.3 remains the current stable line. A stable v6 would mean rechecking the markup. The alpha makes the JavaScript ESM-only, so script tags need type=”module”, and it moves responsive class names from an infix to a prefix, so d-md-none becomes md:d-none.