What are Bootstrap animations?
The framework ships no entrance, hover or scroll effects of its own, and only a handful of components move at all. Anything sold as a Bootstrap animation beyond those comes from custom CSS or an add-on.
Every default below comes from Bootstrap 5.3.8, released August 25, 2025 (Bootstrap blog) and still the current release as of October 2026.
Search results for the topic are crowded with UI kits layered on top of the Bootstrap framework. Their animation syntax does nothing in a plain Bootstrap install.
Bootstrap Studio builds its scroll effects on the AOS library and its hover effects on Animate.css, and the docs say scroll animations are switched off on mobile devices. MDBootstrap advertises 70+ CSS-only animations, all driven by its own data-mdb-animation attributes.
Even inside a single kit the syntax moved around. MDB’s Bootstrap 4 docs use an .animated class, its older Bootstrap 5 docs use data-mdb-toggle=”animation”, and the current docs require data-mdb-animation-init.
Which Bootstrap components animate by default?
Fades, collapse, the carousel, spinners, placeholders and striped progress bars are everything that moves out of the box. All but the placeholders read their timing from a Sass variable, and the placeholder loops hard-code a 2-second cycle (Bootstrap 5.3.8 source).
| Component and class | What moves | Default timing | Sass variable |
|---|
| Fade (.fade) | Opacity | .15s linear | $transition-fade |
| Collapse (.collapsing) | Height, or width with .collapse-horizontal | .35s ease | $transition-collapse, $transition-collapse-width |
| Carousel (.carousel-item) | Transform, or opacity with .carousel-fade | .6s ease-in-out | $carousel-transition-duration, $carousel-transition |
| Spinners (.spinner-border, .spinner-grow) | Rotation, or scale and opacity | .75s per cycle | $spinner-animation-speed |
| Animated stripes (.progress-bar-animated) | Stripe position | 1s linear infinite | $progress-bar-animation-timing |
These timings live in scss/_variables.scss, so a single override retimes every instance on the page.
Bootstrap’s take on skeleton screens, added in v5.1, has two animated styles. .placeholder-glow pulses opacity from .5 down to .2 and back, and .placeholder-wave slides a gradient mask across the block.
The Bootstrap carousel slides by default and crossfades once you add .carousel-fade. Text-only slides usually need .bg-body or some custom CSS to crossfade cleanly.
Padding can’t go on a .collapse element itself, so put it on an inner wrapper. Collapse and accordion share one set of transition classes in scss/_transitions.scss.
The modal reuses .fade for opacity but slides its dialog with its own $modal-transition and $modal-fade-transform. Changing $transition-fade alone won’t retime that slide.
How to change animation speed and easing in Bootstrap
Set a new value for the component’s transition variable (for example $transition-collapse or $carousel-transition-duration) before importing Bootstrap’s Sass, or overwrite the compiled CSS rule on a CDN build. Sass retimes every instance at once, while a CSS override only touches the selectors you write.
- Find the variable you need. $transition-fade, $transition-collapse, $carousel-transition-duration, $spinner-animation-speed and $btn-transition cover the common cases.
- In your own .scss file, write something like $transition-collapse: height .6s ease-in-out; above the Bootstrap import.
- Compile it and load the resulting CSS in place of the stock bootstrap.min.css.
- With no build step, write the same value into a rule that loads after Bootstrap, for example .collapsing { transition: height .6s ease-in-out; }
- If you’re stacking transitions on the carousel, keep transform first, as in transition: transform 2s ease, opacity .5s ease-out (Bootstrap carousel docs).
One catch. $transition-base (all .2s ease-in-out) is the general default, but buttons read $btn-transition at .15s and form controls read $input-transition. Changing the base value alone doesn’t retime them.
Overriding Bootstrap’s compiled CSS only works when your rule loads after Bootstrap’s stylesheet or carries higher specificity. Put it in the wrong place and nothing changes, and the page keeps the stock timing.
Easing matters as much as duration. Bootstrap’s own defaults pair linear with opacity, ease with height and ease-in-out with the carousel’s transform (the modal slide uses ease-out).
To audition a curve before touching Sass, the cubic-bezier editor in your browser’s DevTools shows it live. Then keep the duration short. Stretching a collapse from .35s to a full second just feels laggy.
Turning off transitions and honoring reduced motion
Set $enable-transitions to false to strip Bootstrap’s predefined transitions at build time, and leave $enable-reduced-motion on (its default) so the prefers-reduced-motion media query suppresses motion for users who ask for it. That media query covers Bootstrap’s own components only, not custom keyframes or third-party libraries.
$enable-transitions is true by default. Switch it to false and the transitions Bootstrap adds to components aren’t compiled, which takes the animated progress stripes with them. $enable-reduced-motion is also true by default and turns on the prefers-reduced-motion media query that suppresses certain animations and transitions.
There’s a third switch, $enable-smooth-scroll, also true by default. It applies scroll-behavior: smooth, except for users who ask for reduced motion.
For one component only, drop the class instead. A modal without .fade appears instantly, per Bootstrap’s modal docs.
The docs show the support widening over time. The 4.1 docs limited it to Safari on macOS and iOS, the 4.5 docs added carousel sliding to the disabled list, and the 4.6 and 5.3.8 docs add slowed spinners.
The Safari-only limit is history, since MDN lists prefers-reduced-motion as widely available across browsers since January 2020.
There’s a gap in 5.3.8, though. The placeholder loops carry no prefers-reduced-motion rule, so .placeholder-glow and .placeholder-wave keep animating for users who asked for less motion. A backport (pull request 42769, July 2026) adds the guard on Bootstrap’s main branch, but it isn’t in the 5.3.8 release.
Until a release ships the fix, add it yourself. Set animation to none on .placeholder-glow .placeholder and .placeholder-wave inside @media (prefers-reduced-motion: reduce).
Motion preferences belong to web accessibility work, not the polish pass. WCAG 2.3.3, Animation from Interactions (Level AAA, added in WCAG 2.1), asks that non-essential motion triggered by interaction can be turned off, and the W3C lists the prefers-reduced-motion query as a sufficient technique (C39).
Autoplaying carousels fall under 2.2.2 Pause, Stop, Hide (Level A). That’s why Bootstrap’s carousel docs recommend avoiding autoplay or adding a pause control.
How to add custom animations to Bootstrap elements
Write a @keyframes rule, attach it to a class with animation-duration, animation-delay and animation-fill-mode, and load the stylesheet after Bootstrap. Wrap it in a prefers-reduced-motion guard, because Bootstrap’s reduced-motion handling stops at its own components.
Writing the keyframes
A fade-up on page load takes one @keyframes block and one class. CSS keyframe rules handle it fine: start at opacity 0 with translateY(1rem), end at opacity 1 with translateY(0).
Keep animation-duration inside Bootstrap’s own range, from .15s for fades up to .6s for the carousel. Stagger siblings with animation-delay in steps of .1s, and set animation-fill-mode to both so the first frame holds during the delay and the last frame stays afterward.
Hover effects need a transition, not keyframes. Set transition on the base state and change transform or box-shadow on :hover. Most hover effects written in CSS follow that pattern.
Watch out for one conflict. Carousel items already animate transform through $carousel-transition, and the modal dialog does the same through $modal-fade-transform. Put custom keyframes on an inner wrapper, not on the managed element.
Running code when a Bootstrap transition starts or ends
Events come in pairs. The infinitive form fires when an action starts, and the past participle form fires when it completes.
On a collapse, show.bs.collapse and hide.bs.collapse fire immediately, while shown.bs.collapse and hidden.bs.collapse wait for the CSS transition to finish. The carousel works the same way: slide.bs.carousel fires when the slide method is invoked, and slid.bs.carousel fires once the slide transition has completed.
Every infinitive event accepts preventDefault(), which stops the action before it starts. A method call on a component that is mid-transition is ignored (Bootstrap JavaScript docs), so to reach slide 2 right after slide 1, call carousel.to(‘2’) inside a slid.bs.carousel listener.
jQuery can trip this up. If it’s on the window and the body has no data-bs-no-jquery attribute, the 5.3 docs say Bootstrap emits events through jQuery’s event system, so listeners must use .on or .one instead of addEventListener.
Animate.css, AOS, WOW.js and GSAP compared for Bootstrap projects
Animate.css gives you ready-made keyframe effects. AOS and WOW.js fire effects when an element scrolls into view, and GSAP covers sequenced or scroll-linked motion. WOW.js is the one I’d retire from new projects.
| Library | Trigger | Bootstrap-side syntax | Reduced motion |
|---|
| Animate.css 4.1.1 | Class you add | animate__animated animate__fadeIn | Built in |
| AOS | Viewport entry | data-aos=”fade-up” plus AOS.init() | No guard by default, use the disable option |
| WOW.js | Viewport entry | class=”wow animate__fadeIn” plus new WOW({ animateClass: ‘animate__animated’ }).init() | Only if animateClass is set to animate__animated, so the Animate.css 4.x rule applies |
| GSAP | Timeline or ScrollTrigger | gsap.from(“.card”, { opacity: 0 }) | Your code decides |
Load Bootstrap’s stylesheet first and the library’s second, so your own overrides sit last. Scripts go before the closing body tag, with AOS.init() called right after the AOS script (AOS README).
Animate.css
The Animate.css 4.1.1 source sets –animate-duration and –animate-delay to 1s, then forces a 1ms duration and a single iteration under prefers-reduced-motion.
It’s the best-known CSS animations library, and class swaps alone trigger every effect. The speed classes scale that default: animate__faster runs 500ms, animate__fast 800ms, animate__slow 2s and animate__slower 3s.
Version 4 prefixes every class, so a 3.x snippet like .animated .fadeIn becomes animate__animated animate__fadeIn. The animate.compat.css build keeps the unprefixed names for migrations.
The catalog is large and sits behind a single class prefix. The reduced-motion rule ships inside the file, so there’s nothing to add there, and the speed and delay classes cover timing without custom CSS.
It has no scroll trigger of its own. The license deserves a look too. The main-branch file header lists the Hippocratic License 2.1, where the 4.0.0 header and the 4.1.1 npm build list MIT, so check terms before shipping to a client.
AOS
AOS needs a data-aos attribute on the element, the stylesheet in the head, and AOS.init() after the script tag before the closing body tag.
The defaults are a 400ms duration, a 120px offset, ease easing and once set to false, with duration and delay capped at 3000ms in 50ms steps (AOS README).
On the plus side it takes one attribute per element, needs no custom JavaScript, and has a useClassNames option that applies Animate.css classes from the data-aos value. The downside is that disable defaults to false, so the reduced-motion guard is yours to write, and elements replay on every pass until once is true.
WOW.js
WOW.js is the weakest bet for new work, and its defaults show its age. boxClass is wow and animateClass is animated, the Animate.css 3.x name. mobile defaults to true, so reveals run on phones, and live defaults to true, so content added after init still animates.
The README offers WOW.js under GPLv3 or a paid commercial license, and an MIT-licensed fork’s README marks it temporarily deprecated in favour of AOS. That fork’s planned 3.0 would warn on animation durations with granularity finer than 50ms or longer than 3s, the same limits AOS applies.
If a legacy page already calls new WOW().init(), leave it alone. For anything new, pick AOS.
GSAP
Webflow made GSAP free for everyone on April 30, 2025, plugins included, and widened the standard license to cover commercial use (Webflow update notes).
That removes the cost objection. ScrollTrigger and the other plugins still load as separate imports that you register with gsap.registerPlugin().
Timelines and staggers come from a single control point, and ScrollTrigger handles scroll-linked motion. The cost is a second animation system with its own reduced-motion handling, and it’s overkill for a fade or a hover lift.
How to animate Bootstrap elements on scroll
Hide each element in its starting state and play its animation when it enters the viewport. AOS does that through a data-aos attribute, and the Intersection Observer API does it with a few lines of your own JavaScript.
The Intersection Observer API has been available across browsers since March 2019, per MDN, and Bootstrap’s own scrollspy has run on it since 5.2.0.
- Add the AOS stylesheet in the head and its script before the closing body tag.
- Call AOS.init({ once: true, disable: window.matchMedia(‘(prefers-reduced-motion: reduce)’).matches }) after the script.
- Put data-aos=”fade-up” on the Bootstrap columns or cards, and add data-aos-delay in steps of 100 to stagger siblings.
- To drive Animate.css instead, set useClassNames to true, initClassName to false and animatedClassName to animate__animated (the AOS README example uses the 3.x name, animated).
The native route skips the library. Give the elements a .reveal class with opacity 0 and translateY(1rem), watch them with an IntersectionObserver, add .is-visible on entry, then unobserve so each one animates once.
It’s the script-light version of CSS animations on scroll, since the observer only toggles a class and CSS does the animating. Put the hidden starting state inside @media (prefers-reduced-motion: no-preference), so users who ask for less motion never meet hidden content.
A few things bite people here. once defaults to false in AOS, so elements animate again every time they re-enter the viewport. AOS’s documented custom-animation pattern starts at opacity 0 and reveals on .aos-animate, so a script that never runs leaves the content invisible.
Phones are the other one. AOS’s disable option accepts ‘phone’, ‘tablet’ or ‘mobile’, while WOW.js runs on mobile unless you set mobile to false.
Skip scroll triggers for anything above the fold. Those elements are in view at load, so a hidden starting state only delays them.
When Bootstrap’s built-in animations are not enough
Work through the options in order: native transition, custom CSS or Animate.css, a scroll trigger, then GSAP. Stop at the first one that does the job.
- Stay with a native transition when the component already moves and only timing or easing needs to change. Move on once the effect isn’t a component state change.
- One or two effects, such as a fade-up or a hover lift, are plain CSS work, and a long list of keyframe effects justifies Animate.css. Move on when the trigger has to be scroll position.
- AOS or the Intersection Observer API suits reveals tied to the viewport. Move on when elements must run in sequence.
- GSAP is for timelines, staggers and scroll-linked motion.
The cheapest option that works is the right one.
Button feedback, hover states and focus changes are micro-interactions, and Bootstrap’s native transitions already cover them.
One or two effects cost less as custom CSS animation than as a dependency, and the reduced-motion guard stays in your own stylesheet.
Bootstrap’s JavaScript docs warn that React, Vue and Angular can mutate the same element as Bootstrap and leave dropdowns stuck open. An animation library that writes inline styles onto a Bootstrap-managed element invites the same kind of collision.
The components carry their own limits. The carousel docs say nested carousels are not supported and that carousels in general can cause usability and accessibility challenges.
How Bootstrap animations differ between versions 4, 5 and 6
Bootstrap 5 dropped jQuery and renamed the reduced-motion switch, while Bootstrap 6, still an alpha as of October 29, 2026, moves transitions into CSS tokens and native browser features. Production work belongs on 5.3.x for now.
On release status, the docs label 5.3.x as Latest and 6.0.x as Alpha, and GitHub marks v6.0.0-alpha.1, published October 8, 2026, as a pre-release.
Bootstrap 4 to 5
- jQuery was dropped in 5.0.0, and every plugin was rewritten in plain JavaScript.
- Data attributes got a namespace, so data-toggle became data-bs-toggle.
- The reduced-motion switch $enable-prefers-reduced-motion-media-query became $enable-reduced-motion.
- 5.2.0 added local CSS variables to components, so a spinner’s –bs-spinner-animation-speed changes at runtime without recompiling Sass.
Event names carry over between 4 and 5, so show.bs.collapse and slid.bs.carousel keep working. Native addEventListener replaced jQuery’s on() as the default way to listen.
Bootstrap 5 to 6
Bootstrap 6 raises the minimum browsers to Chrome and Edge 130, Firefox 132 and Safari 18, up from Chrome and Firefox 60 and Safari 12 in Bootstrap 5 (Bootstrap 6 migration guide).
A floor this high turns cross-browser compatibility into an audit before upgrading, and the guide tells teams to check their own analytics first. It also explains why v6 animates with newer CSS: collapse and the accordion use interpolate-size, and browsers without it open and close at once, with Firefox the one to watch.
- $transition-fade and $transition-collapse become –transition-fade-duration, –transition-fade-timing and a matching collapse pair.
- Transition mixins now emit inside a prefers-reduced-motion: no-preference query, so a browser without the feature gets no transition at all.
- .transition-none and .animation-none switch motion off per element, and the decorative .animation-shake and .animation-pop disable themselves under reduced motion.
- .collapsing is gone and the plugin only toggles .show, while the accordion sits on native details and summary.
The carousel is rebuilt on CSS scroll snap, with the ride option renamed autoplay and made a boolean. Modal becomes Dialog and offcanvas becomes Drawer, both on the native dialog element, and toasts, alerts, tabs, tooltips and popovers animate through CSS, so nothing depends on .fade.
On the JavaScript side, show(), hide() and toggle() return promises that resolve when the transition ends, so await collapse.show() can replace a shown.bs.collapse listener. The events still fire, the bundle is ESM-only with no window.bootstrap global, and jQuery support is gone.
Placeholders change names too. .placeholder-wave is the standard shimmer, .placeholder-pulse is the old opacity loop, and .placeholder-glow stays as an alias. The alpha also guards both animations behind prefers-reduced-motion (pull request 42768).
Bootstrap Animations FAQ
How do I make a Bootstrap modal zoom in instead of sliding down?
Set $modal-fade-transform to scale(.8) before compiling, which is the zoom-in example in Bootstrap’s modal docs. On a precompiled build, write that transform onto .modal.fade .modal-dialog in your own stylesheet.
For a panel that enters from the side, use the offcanvas component instead of bending the modal.
Can I speed up or slow down a Bootstrap spinner?
Yes. Set –bs-spinner-animation-speed on the spinner element, for example style=”–bs-spinner-animation-speed: 1.5s”, or change $spinner-animation-speed in Sass for every spinner at once.
The .spinner-border-sm and .spinner-grow-sm modifiers change size only, so the speed value carries over.
What is the difference between data-bs-interval and the slide transition duration?
data-bs-interval sets the pause between automatic slides, 5000ms by default. How long a single slide takes to move comes from $carousel-transition-duration.
Add data-bs-interval to a single .carousel-item to override the pause for that slide only.
Why do text-only slides overlap when I use .carousel-fade?
A crossfade changes opacity, so the outgoing and incoming slides are visible at the same moment. Text slides with no background let the old text show through the new one.
Give each .carousel-item a solid background from the bg utilities, matched to the page color.
How Far to Take Bootstrap Animations
Stay on the native components until a design actually calls for an entrance, hover or scroll effect.
The order that costs least is Sass variable overrides first, custom keyframes second, a scroll library last. Each step adds code to maintain, and Bootstrap’s reduced-motion handling stops applying once an effect is your own. So variables cover timing and easing, keyframes get a prefers-reduced-motion check, and AOS or GSAP only show up when scroll triggers are needed.
A library gets the effect built faster, but it brings an extra script and a manual accessibility check.
This position was verified on October 29, 2026. A stable Bootstrap 6 release would change it, because the new motion utilities and the CSS-based carousel replace several of today’s workarounds.