Sirv Content Hub
Tutorial
February 20, 2026

Bootstrap Images: Responsive Images in Bootstrap 5 (Complete Tutorial)

How to use responsive images in Bootstrap 5. Covers img-fluid, srcset, picture element, lazy loading, and CDN optimization for fast Bootstrap sites.

S
Sirv Team
Bootstrap Images: Responsive Images in Bootstrap 5 (Complete Tutorial)

Bootstrap 5 gives you a handful of CSS classes for images, but drop an img-fluid on every <img> tag and call it a day? That’s how you end up shipping 4 MB product pages to mobile users. The classes handle scaling. They don’t handle what gets downloaded.

This tutorial covers everything from Bootstrap’s built-in image utilities to the real responsive image techniques (srcset, picture element, lazy loading) that actually cut page weight. Code-heavy, theory-light. Let’s get into it.

Bootstrap’s img-fluid Class

The img-fluid class is Bootstrap’s core responsive image utility. It applies two CSS rules:

.img-fluid {
  max-width: 100%;
  height: auto;
}

That’s it. The image scales down to fit its container but never stretches beyond its natural size. The aspect ratio stays locked.

<img src="product.jpg" class="img-fluid" alt="Running shoes on white background">

What img-fluid does: prevents images from overflowing their parent container on small screens.

What img-fluid does NOT do: it doesn’t resize the file being downloaded. A 3000px wide JPEG still downloads at full size on a 375px phone. The browser just renders it smaller. You still pay for every byte.

This is the most common misunderstanding with Bootstrap images. The class is a display fix, not a performance optimization.

Image Utility Classes

Bootstrap ships several utility classes for image styling beyond img-fluid.

Rounded Images

<!-- Slightly rounded corners -->
<img src="avatar.jpg" class="img-fluid rounded" alt="User avatar">

<!-- Fully circular (works best on square images) -->
<img src="avatar.jpg" class="img-fluid rounded-circle" alt="User avatar">

<!-- Rounded on specific sides -->
<img src="banner.jpg" class="img-fluid rounded-top" alt="Banner">

Thumbnail Images

The img-thumbnail class adds a border and padding, giving images a framed look:

<img src="product.jpg" class="img-thumbnail" alt="Product thumbnail">

This renders with a 1px border and 0.25rem padding. It also includes max-width: 100% and height: auto, so you don’t need img-fluid alongside it.

Figure Element

Bootstrap styles the HTML <figure> element for image captions:

<figure class="figure">
  <img src="chart.png" class="figure-img img-fluid rounded" alt="Monthly sales chart">
  <figcaption class="figure-caption text-center">
    Monthly sales data for Q4 2025
  </figcaption>
</figure>

The figure-img class adds bottom margin between the image and caption. Combine it with img-fluid and any rounding utilities you want.

Images Inside Buttons

Placing images inside Bootstrap buttons is straightforward. The key is controlling the image size so it doesn’t blow out the button dimensions:

<!-- Icon button with image -->
<button type="button" class="btn btn-primary d-inline-flex align-items-center gap-2">
  <img src="cart-icon.svg" alt="" width="20" height="20">
  Add to Cart
</button>

<!-- Image-only button -->
<button type="button" class="btn btn-outline-secondary p-1">
  <img src="play-icon.svg" alt="Play video" width="24" height="24">
</button>

A few things to note. Set explicit width and height on the image so it doesn’t resize unpredictably. Use d-inline-flex and align-items-center on the button to vertically center the icon with the text. The gap-2 class adds spacing between image and text without manual margins.

For background images on buttons, CSS is a better fit than inline <img> tags:

<button type="button" class="btn btn-dark text-white position-relative overflow-hidden"
        style="background-image: url('texture.jpg'); background-size: cover;">
  Shop the Collection
</button>

Images in Bootstrap Cards

Cards are one of the most common places you’ll use images in Bootstrap. The card-img-top class positions an image at the top of a card:

<div class="card" style="width: 18rem;">
  <img src="sneakers.jpg" class="card-img-top" alt="Nike Pegasus 41"
       width="400" height="300" loading="lazy">
  <div class="card-body">
    <h5 class="card-title">Nike Pegasus 41</h5>
    <p class="card-text">Lightweight running shoe for daily training.</p>
    <a href="#" class="btn btn-primary">View Details</a>
  </div>
</div>

For overlay text on top of an image:

<div class="card text-bg-dark">
  <img src="hero-shoes.jpg" class="card-img" alt="Collection banner"
       width="800" height="400">
  <div class="card-img-overlay d-flex flex-column justify-content-end">
    <h5 class="card-title">Spring Collection</h5>
    <p class="card-text">New arrivals dropping this week.</p>
  </div>
</div>

When building product grids, combine cards with Bootstrap’s grid system and make sure every image has the same aspect ratio. Otherwise your grid looks jangled:

<div class="row row-cols-1 row-cols-md-3 g-4">
  <div class="col">
    <div class="card h-100">
      <img src="product-1.jpg" class="card-img-top"
           alt="Adidas Superstar" width="400" height="400"
           style="object-fit: cover; aspect-ratio: 1/1;" loading="lazy">
      <div class="card-body">
        <h5 class="card-title">Adidas Superstar</h5>
        <p class="card-text">$95.00</p>
      </div>
    </div>
  </div>
  <!-- repeat for more products -->
</div>

The object-fit: cover and aspect-ratio: 1/1 keep every card image consistent even if the source images have different dimensions.

Hero Sections with Bootstrap

Hero images set the tone for a page. Here’s a full-width hero using Bootstrap utilities:

<div class="position-relative overflow-hidden" style="height: 60vh;">
  <img src="hero-landscape.jpg"
       class="w-100 h-100"
       style="object-fit: cover;"
       alt="Mountain landscape at sunrise"
       width="1600" height="900"
       fetchpriority="high">
  <div class="position-absolute top-50 start-50 translate-middle text-center text-white">
    <h1 class="display-3 fw-bold">Explore the Outdoors</h1>
    <p class="lead">Adventure gear for every trail.</p>
    <a href="#" class="btn btn-light btn-lg mt-3">Shop Now</a>
  </div>
</div>

Notice fetchpriority="high" on the hero image. This tells the browser to prioritize downloading it since it’s your Largest Contentful Paint (LCP) element. Never lazy load a hero image.

The picture Element with Bootstrap

Bootstrap’s CSS classes work fine inside <picture> elements. Use <picture> when you need different image crops at different breakpoints (this is called art direction):

<picture>
  <!-- Mobile: tall portrait crop -->
  <source media="(max-width: 576px)"
          srcset="hero-mobile.jpg">

  <!-- Tablet: medium crop -->
  <source media="(max-width: 992px)"
          srcset="hero-tablet.jpg">

  <!-- Desktop: wide landscape -->
  <img src="hero-desktop.jpg"
       class="img-fluid w-100"
       alt="Product showcase"
       width="1400" height="500"
       style="object-fit: cover;">
</picture>

The Bootstrap classes go on the <img> fallback element, not on the <source> tags. The browser picks the right source, then applies whatever classes are on the <img>.

You can also use <picture> for format switching (serving WebP to browsers that support it):

<picture>
  <source srcset="product.avif" type="image/avif">
  <source srcset="product.webp" type="image/webp">
  <img src="product.jpg" class="img-fluid" alt="Product photo"
       width="800" height="600">
</picture>

Though if you’re using a CDN that auto-detects format support, you won’t need this manual approach.

srcset for Resolution Switching

This is where the real performance gains happen. While img-fluid scales the display, srcset controls which file the browser downloads:

<img src="product.jpg"
     srcset="product-400.jpg 400w,
             product-800.jpg 800w,
             product-1200.jpg 1200w,
             product-1600.jpg 1600w"
     sizes="(max-width: 576px) 100vw,
            (max-width: 992px) 50vw,
            33vw"
     class="img-fluid"
     alt="Running shoes"
     width="800" height="600"
     loading="lazy">

How the browser picks

The sizes attribute tells the browser how wide the image will render at each breakpoint. The browser combines that with the device pixel ratio (DPR) to pick the smallest adequate file from srcset.

On a 375px iPhone (3x DPR) where sizes says 100vw:

  • Rendered width: 375px
  • Pixels needed: 375 x 3 = 1,125
  • Browser picks: product-1200.jpg (smallest file that covers 1,125px)

On a 1440px desktop (1x DPR) where sizes says 33vw:

  • Rendered width: ~475px
  • Pixels needed: 475
  • Browser picks: product-800.jpg

Match sizes to Bootstrap breakpoints

Bootstrap’s grid breakpoints are 576px, 768px, 992px, and 1200px. Your sizes attribute should mirror your actual layout:

<!-- Full width on mobile, 2 columns on md, 3 columns on lg -->
sizes="(max-width: 576px) 100vw,
       (max-width: 992px) 50vw,
       33vw"

<!-- Full width until xl, then contained at 1140px max -->
sizes="(max-width: 1200px) 100vw, 1140px"

Background Images with Bootstrap

Bootstrap 5 doesn’t include background image utility classes, but you can combine its layout utilities with inline styles or custom CSS:

<div class="d-flex align-items-center justify-content-center text-white text-center"
     style="background-image: url('hero-bg.jpg');
            background-size: cover;
            background-position: center;
            min-height: 400px;">
  <div class="p-5">
    <h2 class="display-5 fw-bold">Summer Sale</h2>
    <p class="lead">Up to 50% off selected items.</p>
  </div>
</div>

For responsive background images, use media queries in a <style> block or your stylesheet:

.hero-bg {
  background-image: url('hero-mobile.jpg');
  background-size: cover;
  background-position: center;
  min-height: 300px;
}

@media (min-width: 768px) {
  .hero-bg {
    background-image: url('hero-tablet.jpg');
    min-height: 400px;
  }
}

@media (min-width: 1200px) {
  .hero-bg {
    background-image: url('hero-desktop.jpg');
    min-height: 500px;
  }
}

One downside of CSS background images: the browser can’t use srcset or sizes, so you lose automatic resolution switching. Use <img> tags when possible for better browser optimization.

Lazy Loading with Bootstrap

Native lazy loading works perfectly alongside Bootstrap’s image classes:

<!-- Below-the-fold product grid images -->
<img src="product.jpg"
     class="img-fluid card-img-top"
     alt="Product"
     width="400" height="400"
     loading="lazy">

The loading="lazy" attribute tells the browser to defer loading until the image is near the viewport. Two rules to follow:

  1. Never lazy load above-the-fold images. Your hero image, first visible product image, and logo should load immediately. Either omit loading (defaults to eager) or explicitly set loading="eager".

  2. Always include width and height. Without dimensions, the browser can’t reserve space for lazy-loaded images, causing layout shift when they pop in.

For the hero or LCP image, go further with fetchpriority:

<img src="hero.jpg"
     class="img-fluid w-100"
     alt="Hero banner"
     width="1600" height="600"
     fetchpriority="high">

Common Mistakes (and How to Fix Them)

1. Missing width and height attributes

This is the biggest source of Cumulative Layout Shift (CLS) on Bootstrap sites. When the browser doesn’t know an image’s dimensions upfront, the page jumps around as images load.

<!-- BAD: causes layout shift -->
<img src="product.jpg" class="img-fluid" alt="Product">

<!-- GOOD: browser reserves space -->
<img src="product.jpg" class="img-fluid" alt="Product"
     width="800" height="600">

Modern browsers calculate the aspect ratio from width and height, then combine it with img-fluid’s max-width: 100%; height: auto; to reserve the right amount of space before the image loads. Zero layout shift.

2. Shipping oversized images

Adding img-fluid to a 4000px wide image doesn’t make your page faster. The browser still downloads the full file. Always pair img-fluid with properly sized source files or use srcset to give the browser options.

3. Lazy loading the hero image

If your LCP element has loading="lazy", the browser intentionally delays loading it. This tanks your Core Web Vitals scores. Only lazy load images that are below the fold on initial page load.

4. Using img-thumbnail for everything

The img-thumbnail class adds borders and padding. It’s meant for actual thumbnail presentations, not as a general-purpose responsive class. Use img-fluid for standard responsive behavior.

5. Ignoring aspect ratio in grids

When product images in a Bootstrap grid have different aspect ratios, the layout looks broken. Force consistent ratios with CSS:

.product-image {
  aspect-ratio: 1 / 1;
  object-fit: cover;
  width: 100%;
}

Page Weight: The Real Impact

How much difference does proper image handling make? Here’s a comparison based on a typical product listing page with 12 images:

Average Page Weight (KB) by Image Strategy

Based on a product listing page with 12 images

Notice that img-fluid alone doesn’t reduce page weight at all. It only changes how images display, not what gets downloaded. Adding srcset cuts weight by more than half because the browser can pick appropriately sized files. A CDN takes it further by also converting formats (WebP, AVIF) and applying smart compression.

Using a CDN for Automatic Responsive Images

Managing multiple image sizes by hand is tedious. For every product image, you’d need to export 4-5 width variants, potentially in multiple formats. On a catalog with 500 products, that’s thousands of files.

An image CDN like Sirv removes this entirely. You upload one high-resolution original, and the CDN generates any size or format on the fly through URL parameters:

<!-- Single source image, multiple sizes via URL params -->
<img src="https://demo.sirv.com/shoes.jpg?w=800&format=webp"
     srcset="https://demo.sirv.com/shoes.jpg?w=400&format=webp 400w,
             https://demo.sirv.com/shoes.jpg?w=800&format=webp 800w,
             https://demo.sirv.com/shoes.jpg?w=1200&format=webp 1200w"
     sizes="(max-width: 576px) 100vw,
            (max-width: 992px) 50vw,
            33vw"
     class="img-fluid"
     alt="Running shoes"
     width="800" height="600"
     loading="lazy">

Some useful Sirv URL parameters for Bootstrap projects:

?w=400              → Resize to 400px wide
?w=400&h=400        → Fit within 400x400 box
?format=webp        → Force WebP output
?q=80               → Set quality to 80%
?w=400&format=webp  → Combine resize + format
?crop.type=face     → Smart crop centered on faces
?profile=thumbnail  → Apply a saved preset

Sirv also auto-detects the browser’s format support. If you skip the format parameter, it reads the Accept header and serves AVIF to Chrome, WebP to Safari, and JPEG to everything else. That means you can simplify your markup:

<img src="https://demo.sirv.com/shoes.jpg?w=800"
     srcset="https://demo.sirv.com/shoes.jpg?w=400 400w,
             https://demo.sirv.com/shoes.jpg?w=800 800w,
             https://demo.sirv.com/shoes.jpg?w=1200 1200w"
     sizes="(max-width: 576px) 100vw,
            (max-width: 992px) 50vw,
            33vw"
     class="img-fluid"
     alt="Running shoes"
     width="800" height="600"
     loading="lazy">

No <picture> element needed for format switching. The CDN handles it automatically. You only need <picture> if you’re doing art direction (different crops at different breakpoints).

For the simplest possible setup, Sirv’s JavaScript can handle responsive delivery automatically:

<img class="Sirv" data-src="https://demo.sirv.com/shoes.jpg" alt="Running shoes">
<script src="https://scripts.sirv.com/sirvjs/v3/sirv.js"></script>

This detects the rendered size, requests the right dimensions, serves the best format, and adds lazy loading. No srcset or sizes to write at all.

Quick Reference Checklist

  • Add img-fluid to all content images for responsive scaling
  • Set width and height on every <img> to prevent CLS
  • Use srcset and sizes to serve right-sized files (not just right-sized display)
  • Match sizes values to your Bootstrap grid layout
  • Add loading="lazy" to below-the-fold images only
  • Set fetchpriority="high" on your LCP/hero image
  • Use <picture> for art direction (different crops), not format switching
  • Use object-fit: cover with aspect-ratio for consistent grids
  • Consider an image CDN to generate responsive variants from a single upload
  • Test in Chrome DevTools Network panel to verify correct file sizes are loading

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial