Bootstrap Cards: 17 Examples for Grids, Products and Profiles

A Bootstrap card has no width and no margin of its own, so it stretches to fill whatever contains it. Version 5 also removed card-deck and card-columns, which leaves rows, columns and the optional Masonry plugin to place cards.

Below are 17 Bootstrap card examples you can copy, followed by a guide to card markup, layouts, styling and the changes from version 4.

By Bogdan Sandu ยท Updated October 29, 2026

Every example runs on Bootstrap 5.3.8 from the jsDelivr CDN. Add these tags to your page, then paste an example's HTML, CSS and JS.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css" rel="stylesheet" integrity="sha384-nWN3KqaaVcbEsCjLSV+2jMjh62Iny/+mN3srM8TZeM8N6aj7eDl5wwYggjAJ4/sJ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>

Card grids and layouts

Bootstrap 5 places cards with the grid: row-cols for responsive rows, h-100 for equal heights, card-group for attached strips and the grid again for horizontal cards. The last demo shows a plugin-free answer to the removed card-columns.

Blog post grid with equal heights

  • row-cols-*
  • g-4
  • h-100
  • card-footer

The row carries row-cols-1 row-cols-md-3 g-4, each card sits in a col, and h-100 stretches every card to the row height so the footers line up even though the excerpts differ in length. The only custom CSS fixes the image ratio with object-fit.

View code
<div class="bcd-blog w-100">
  <div class="row row-cols-1 row-cols-md-3 g-4">
    <div class="col">
      <article class="card h-100 shadow-sm">
        <img src="fjord.webp" class="card-img-top bcd-blog-img" alt="A deep blue fjord between steep granite cliffs" width="800" height="600">
        <div class="card-body">
          <span class="badge text-bg-primary mb-2">Travel</span>
          <h3 class="card-title h5">Seven days on the Norwegian fjords</h3>
          <p class="card-text text-body-secondary">Ferries, cliff hikes and the one viewpoint worth the 4 a.m. alarm.</p>
        </div>
        <div class="card-footer bg-transparent small text-body-secondary d-flex justify-content-between">
          <span>Mar 4, 2026</span><span>6 min read</span>
        </div>
      </article>
    </div>
    <div class="col">
      <article class="card h-100 shadow-sm">
        <img src="brick-alley.webp" class="card-img-top bcd-blog-img" alt="A narrow brick alley with fire escapes and office towers behind it" width="800" height="600">
        <div class="card-body">
          <span class="badge text-bg-success mb-2">Cities</span>
          <h3 class="card-title h5">The back alleys of an old port town</h3>
          <p class="card-text text-body-secondary">Murals, coffee counters and the warehouse blocks that survived three rebuilds, mapped on foot in a single afternoon.</p>
        </div>
        <div class="card-footer bg-transparent small text-body-secondary d-flex justify-content-between">
          <span>Feb 18, 2026</span><span>9 min read</span>
        </div>
      </article>
    </div>
    <div class="col">
      <article class="card h-100 shadow-sm">
        <img src="city-skyline.webp" class="card-img-top bcd-blog-img" alt="A city skyline at sunrise seen from a high tower" width="800" height="600">
        <div class="card-body">
          <span class="badge text-bg-dark mb-2">Photo</span>
          <h3 class="card-title h5">Shooting skylines at blue hour</h3>
          <p class="card-text text-body-secondary">Settings, timing and where to stand.</p>
        </div>
        <div class="card-footer bg-transparent small text-body-secondary d-flex justify-content-between">
          <span>Jan 29, 2026</span><span>4 min read</span>
        </div>
      </article>
    </div>
  </div>
</div>

Attached card group itinerary

  • card-group
  • card-footer
  • text-body-secondary

card-group joins the cards into one strip with shared borders and equal heights, stacking them below the sm breakpoint (576px). The footers align across the strip without any extra class, which suits a day-by-day itinerary.

View code
<div class="card-group bcd-group w-100 shadow-sm">
  <div class="card">
    <img src="highland-road.webp" class="card-img-top bcd-group-img" alt="A single-track road winding below green highland cliffs" width="800" height="600">
    <div class="card-body">
      <h3 class="card-title h6 text-uppercase text-body-secondary mb-1">Day 1</h3>
      <p class="card-text fw-semibold mb-1">Drive the ridge road</p>
      <p class="card-text small text-body-secondary">Pick up the car at nine and take the single-track road north before the tour buses.</p>
    </div>
    <div class="card-footer small text-body-secondary">142 km, 3 h driving</div>
  </div>
  <div class="card">
    <img src="forest-waterfall.webp" class="card-img-top bcd-group-img" alt="A tall waterfall dropping into a mossy forest gorge" width="800" height="600">
    <div class="card-body">
      <h3 class="card-title h6 text-uppercase text-body-secondary mb-1">Day 2</h3>
      <p class="card-text fw-semibold mb-1">Waterfall loop</p>
      <p class="card-text small text-body-secondary">A short, steep forest loop.</p>
    </div>
    <div class="card-footer small text-body-secondary">8 km hike, 2 h 30 min</div>
  </div>
  <div class="card">
    <img src="coast-road.webp" class="card-img-top bcd-group-img" alt="A coastal road on forested cliffs above a calm sea" width="800" height="600">
    <div class="card-body">
      <h3 class="card-title h6 text-uppercase text-body-secondary mb-1">Day 3</h3>
      <p class="card-text fw-semibold mb-1">Coast road and ferry</p>
      <p class="card-text small text-body-secondary">Follow the cliffs south, stop at the lighthouse and catch the 17:40 ferry back to the mainland.</p>
    </div>
    <div class="card-footer small text-body-secondary">96 km, ferry included</div>
  </div>
</div>

Horizontal card with image left

  • row g-0
  • col-md-4
  • rounded-start
  • object-fit-cover

The docs pattern: a row with g-0 inside the card, the image in col-md-4 with rounded-start and the body in col-md-8. Below 768px the columns stack. Bootstrap utilities object-fit-cover and h-100 make the photo fill its column, and the custom CSS only swaps the corner rounding when stacked.

View code
<div class="card bcd-horiz w-100 shadow-sm overflow-hidden">
  <div class="row g-0">
    <div class="col-md-4">
      <img src="canal-houses.webp" class="img-fluid h-100 object-fit-cover bcd-horiz-img" alt="Old gabled houses along a canal with small boats moored in front" width="800" height="600">
    </div>
    <div class="col-md-8">
      <div class="card-body p-4">
        <div class="d-flex align-items-center gap-2 small text-body-secondary mb-2">
          <i class="bi bi-geo-alt" aria-hidden="true"></i><span>Old town, 0.4 km from center</span>
        </div>
        <h3 class="card-title h5 mb-2">Canal House Apartments</h3>
        <p class="card-text text-body-secondary">Two-bedroom loft above the water with a kitchen, washer and bikes to borrow. Check-in from 15:00.</p>
        <div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
          <p class="mb-0"><span class="fs-4 fw-bold">&euro;148</span> <span class="text-body-secondary">/ night</span></p>
          <a href="#" class="btn btn-primary px-4 py-2">Check dates</a>
        </div>
      </div>
    </div>
  </div>
</div>

Masonry-style columns without a plugin

  • card
  • CSS columns
  • break-inside

Bootstrap 5 removed card-columns and its docs point to the Masonry plugin instead. For a mixed board you can get close with a few lines of CSS: CSS multi-column layout plus break-inside: avoid keeps each card whole. Cards flow down each column, not across rows, exactly like the old card-columns.

View code
<div class="bcd-masonry w-100">
  <div class="card shadow-sm">
    <img src="jellyfish.webp" class="card-img-top" alt="An orange jellyfish with long tentacles in deep blue water" width="800" height="600">
    <div class="card-body">
      <h3 class="card-title h6">Night dive log</h3>
      <p class="card-text small text-body-secondary mb-0">Sea nettles drifting past the reef wall at 18 m.</p>
    </div>
  </div>
  <div class="card text-bg-dark">
    <div class="card-body">
      <blockquote class="blockquote mb-2 fs-6">
        <p>Pack half the clothes and twice the snacks you think you need.</p>
      </blockquote>
      <p class="small mb-0 opacity-75">Trail rule number one</p>
    </div>
  </div>
  <div class="card shadow-sm">
    <div class="card-body">
      <h3 class="card-title h6">Packing list</h3>
      <ul class="small text-body-secondary mb-0 ps-3">
        <li>Shell jacket</li>
        <li>Headlamp and spare batteries</li>
        <li>Water filter</li>
        <li>Paper map</li>
      </ul>
    </div>
  </div>
  <div class="card shadow-sm">
    <img src="snow-camp.webp" class="card-img-top" alt="Yellow tents pitched on snow below sharp mountain peaks" width="800" height="600">
    <div class="card-body">
      <h3 class="card-title h6 mb-0">Base camp, day 4</h3>
    </div>
  </div>
  <div class="card border-primary-subtle bg-primary-subtle">
    <div class="card-body">
      <h3 class="card-title h6 text-primary-emphasis">Weather window</h3>
      <p class="card-text small text-primary-emphasis mb-0">Clear skies Thursday and Friday, wind under 20 km/h above the ridge.</p>
    </div>
  </div>
  <div class="card shadow-sm">
    <img src="granite-valley.webp" class="card-img-top" alt="Granite cliffs above pine trees and a shallow river" width="800" height="600">
  </div>
</div>

Product, pricing and dashboard cards

Cards that sell and report: a product tile with a working add-to-cart button, pricing plans with a billing switch, KPI tiles and an order summary built from a flush list group.

Product card with add-to-cart

  • card-img-top
  • badge
  • --bs-btn-*
  • aria-live

Click Add to cart.

A shop tile: sale badge positioned over the image, rating, price with the old price struck through and a full-width button. The button is themed with --bs-btn-* variables and a few lines of JS switch it to an added state while a visually hidden live region announces the change.

View code
<div class="card bcd-product w-100 shadow-sm">
  <div class="position-relative">
    <img src="snow-camp.webp" class="card-img-top bcd-product-img" alt="Two yellow dome tents pitched on snow" width="800" height="600">
    <span class="badge rounded-pill bg-danger position-absolute top-0 start-0 m-3">-20%</span>
  </div>
  <div class="card-body">
    <p class="small text-body-secondary mb-1">Tents</p>
    <h3 class="card-title h5 mb-1">Ridgeline 2P Dome Tent</h3>
    <div class="d-flex align-items-center gap-1 small text-warning-emphasis mb-3">
      <i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-half" aria-hidden="true"></i>
      <span class="visually-hidden">Rated 4.5 out of 5</span>
      <span class="text-body-secondary ms-1">(312)</span>
    </div>
    <p class="mb-3"><span class="fs-4 fw-bold">$279</span> <del class="text-body-secondary ms-1">$349</del></p>
    <button type="button" class="btn bcd-cart-btn w-100" id="bcd-cart-btn" aria-pressed="false">
      <i class="bi bi-bag-plus me-2" aria-hidden="true"></i><span class="bcd-cart-label">Add to cart</span>
    </button>
    <p class="visually-hidden" id="bcd-cart-status" aria-live="polite"></p>
  </div>
</div>

Pricing cards with a highlighted plan

  • row-cols-md-3
  • btn-check
  • --bs-card-*
  • border-primary

Switch to Yearly.

Three plans in a row-cols-md-3 grid with h-100, the middle one lifted by a thicker border set through --bs-card-border-color and --bs-card-border-width and a badge. A btn-check radio pair switches between monthly and yearly prices that the JS reads from data attributes.

View code
<div class="bcd-pricing w-100">
  <div class="d-flex justify-content-center mb-4">
    <div class="btn-group bcd-billing" role="group" aria-label="Billing period">
      <input type="radio" class="btn-check" name="bcd-billing" id="bcd-bill-month" value="monthly" checked>
      <label class="btn btn-outline-dark px-4" for="bcd-bill-month">Monthly</label>
      <input type="radio" class="btn-check" name="bcd-billing" id="bcd-bill-year" value="yearly">
      <label class="btn btn-outline-dark px-4" for="bcd-bill-year">Yearly <span class="small opacity-75">-20%</span></label>
    </div>
  </div>
  <div class="row row-cols-1 row-cols-md-3 g-4 align-items-stretch">
    <div class="col">
      <div class="card bcd-plan h-100">
        <div class="card-body p-4 d-flex flex-column">
          <h3 class="h6 text-uppercase text-body-secondary">Starter</h3>
          <p class="mb-3"><span class="display-6 fw-bold bcd-price" data-monthly="12" data-yearly="10">$12</span> <span class="text-body-secondary">/ month</span></p>
          <ul class="list-unstyled small mb-4 flex-grow-1">
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>3 projects</li>
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>5 GB storage</li>
            <li><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>Email support</li>
          </ul>
          <a href="#" class="btn btn-outline-dark py-2">Choose Starter</a>
        </div>
      </div>
    </div>
    <div class="col">
      <div class="card bcd-plan bcd-plan-featured h-100 shadow">
        <div class="card-body p-4 d-flex flex-column">
          <div class="d-flex justify-content-between align-items-center">
            <h3 class="h6 text-uppercase bcd-plan-accent mb-0">Studio</h3>
            <span class="badge rounded-pill bcd-plan-badge">Most popular</span>
          </div>
          <p class="mb-3 mt-2"><span class="display-6 fw-bold bcd-price" data-monthly="29" data-yearly="23">$29</span> <span class="text-body-secondary">/ month</span></p>
          <ul class="list-unstyled small mb-4 flex-grow-1">
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>Unlimited projects</li>
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>100 GB storage</li>
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>Client review links</li>
            <li><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>Priority chat support</li>
          </ul>
          <a href="#" class="btn bcd-plan-btn py-2">Choose Studio</a>
        </div>
      </div>
    </div>
    <div class="col">
      <div class="card bcd-plan h-100">
        <div class="card-body p-4 d-flex flex-column">
          <h3 class="h6 text-uppercase text-body-secondary">Agency</h3>
          <p class="mb-3"><span class="display-6 fw-bold bcd-price" data-monthly="79" data-yearly="63">$79</span> <span class="text-body-secondary">/ month</span></p>
          <ul class="list-unstyled small mb-4 flex-grow-1">
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>Everything in Studio</li>
            <li class="mb-2"><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>10 team seats</li>
            <li><i class="bi bi-check2 text-success me-2" aria-hidden="true"></i>SSO and audit log</li>
          </ul>
          <a href="#" class="btn btn-outline-dark py-2">Contact sales</a>
        </div>
      </div>
    </div>
  </div>
</div>

Dashboard KPI cards

  • row-cols-2
  • bg-*-subtle
  • badge
  • inline SVG

Four stat cards in a row-cols-1 row-cols-sm-2 row-cols-md-4 grid. Each uses an icon tile colored with bg-*-subtle and text-*-emphasis, a delta badge and a tiny inline SVG sparkline that inherits currentColor. The custom CSS only sizes the icon tile and sparkline and switches the numbers to tabular figures.

View code
<div class="bcd-kpi w-100">
  <div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 g-3">
    <div class="col">
      <div class="card h-100 border-0 shadow-sm">
        <div class="card-body">
          <div class="d-flex justify-content-between align-items-start mb-3">
            <span class="bcd-kpi-icon bg-primary-subtle text-primary-emphasis rounded-3"><i class="bi bi-currency-dollar" aria-hidden="true"></i></span>
            <span class="badge text-bg-success">+12.4%</span>
          </div>
          <p class="small text-body-secondary mb-1">Revenue</p>
          <p class="h3 fw-bold mb-2 bcd-kpi-num">$48,920</p>
          <svg class="bcd-kpi-spark text-primary" viewBox="0 0 100 24" aria-hidden="true"><polyline points="0,20 15,17 30,18 45,12 60,13 75,7 100,4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </div>
      </div>
    </div>
    <div class="col">
      <div class="card h-100 border-0 shadow-sm">
        <div class="card-body">
          <div class="d-flex justify-content-between align-items-start mb-3">
            <span class="bcd-kpi-icon bg-success-subtle text-success-emphasis rounded-3"><i class="bi bi-people" aria-hidden="true"></i></span>
            <span class="badge text-bg-success">+3.1%</span>
          </div>
          <p class="small text-body-secondary mb-1">Active users</p>
          <p class="h3 fw-bold mb-2 bcd-kpi-num">8,214</p>
          <svg class="bcd-kpi-spark text-success" viewBox="0 0 100 24" aria-hidden="true"><polyline points="0,16 15,15 30,17 45,13 60,11 75,12 100,8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </div>
      </div>
    </div>
    <div class="col">
      <div class="card h-100 border-0 shadow-sm">
        <div class="card-body">
          <div class="d-flex justify-content-between align-items-start mb-3">
            <span class="bcd-kpi-icon bg-warning-subtle text-warning-emphasis rounded-3"><i class="bi bi-cart3" aria-hidden="true"></i></span>
            <span class="badge text-bg-danger">-1.8%</span>
          </div>
          <p class="small text-body-secondary mb-1">Conversion</p>
          <p class="h3 fw-bold mb-2 bcd-kpi-num">3.42%</p>
          <svg class="bcd-kpi-spark text-danger" viewBox="0 0 100 24" aria-hidden="true"><polyline points="0,6 15,8 30,7 45,11 60,10 75,14 100,15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </div>
      </div>
    </div>
    <div class="col">
      <div class="card h-100 border-0 shadow-sm">
        <div class="card-body">
          <div class="d-flex justify-content-between align-items-start mb-3">
            <span class="bcd-kpi-icon bg-info-subtle text-info-emphasis rounded-3"><i class="bi bi-clock-history" aria-hidden="true"></i></span>
            <span class="badge text-bg-secondary">0.0%</span>
          </div>
          <p class="small text-body-secondary mb-1">Avg. session</p>
          <p class="h3 fw-bold mb-2 bcd-kpi-num">4m 12s</p>
          <svg class="bcd-kpi-spark text-info-emphasis" viewBox="0 0 100 24" aria-hidden="true"><polyline points="0,12 15,13 30,11 45,12 60,13 75,11 100,12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </div>
      </div>
    </div>
  </div>
</div>

Order summary with a flush list group

  • list-group-flush
  • card-header
  • card-footer
  • d-flex

A list-group-flush sits directly inside the card, between the card-header and card-footer, so its rows run edge to edge with no extra wrapper. Flex utilities lay out each line item, and the total lives in the footer above a full-width checkout button.

View code
<div class="card bcd-order w-100 shadow-sm">
  <div class="card-header bg-body d-flex justify-content-between align-items-center py-3">
    <h3 class="h6 mb-0">Order summary</h3>
    <a href="#" class="small link-secondary">Edit cart</a>
  </div>
  <ul class="list-group list-group-flush">
    <li class="list-group-item d-flex gap-3 align-items-center py-3">
      <img src="snow-camp.webp" class="rounded bcd-order-thumb" alt="" width="800" height="600">
      <div class="flex-grow-1 min-w-0">
        <p class="mb-0 fw-medium">Ridgeline 2P Dome Tent</p>
        <p class="mb-0 small text-body-secondary">Qty 1</p>
      </div>
      <span class="fw-medium">$279.00</span>
    </li>
    <li class="list-group-item d-flex gap-3 align-items-center py-3">
      <img src="mountain-hiker.webp" class="rounded bcd-order-thumb" alt="" width="800" height="600">
      <div class="flex-grow-1 min-w-0">
        <p class="mb-0 fw-medium">Summit 45L Backpack</p>
        <p class="mb-0 small text-body-secondary">Qty 1</p>
      </div>
      <span class="fw-medium">$164.00</span>
    </li>
    <li class="list-group-item d-flex justify-content-between small text-body-secondary">
      <span>Shipping</span><span>Free</span>
    </li>
    <li class="list-group-item d-flex justify-content-between small text-body-secondary">
      <span>Tax</span><span>$35.44</span>
    </li>
  </ul>
  <div class="card-footer bg-body py-3">
    <div class="d-flex justify-content-between fw-bold mb-3">
      <span>Total</span><span>$478.44</span>
    </div>
    <a href="#" class="btn btn-dark w-100 py-2">Checkout</a>
  </div>
</div>

Profile, event and image cards

Content cards for people, quotes, events and photos, including an overlay card that keeps its text readable and a fully clickable card made with stretched-link.

Profile card with a follow toggle

  • card-img-top
  • rounded-circle
  • data-bs-toggle="button"
  • aria-label

Click Follow.

A cover image, an avatar pulled up over it with a negative margin and a white ring, stats in a three-column row and icon-only buttons with aria-label. The Follow button uses the Bootstrap button plugin (data-bs-toggle="button"), which toggles .active and aria-pressed; a short JS listener swaps the label.

View code
<div class="card bcd-profile w-100 shadow-sm text-center">
  <img src="highland-road.webp" class="card-img-top bcd-profile-cover" alt="" width="800" height="600">
  <div class="card-body pt-0">
    <img src="mountain-hiker.webp" class="rounded-circle bcd-profile-avatar" alt="Mara Lindqvist on a mountain trail" width="800" height="600">
    <h3 class="card-title h5 mt-3 mb-0">Mara Lindqvist</h3>
    <p class="text-body-secondary small mb-3">Trail guide and photographer, Tromso</p>
    <div class="row g-0 border rounded-3 py-2 mb-3">
      <div class="col"><p class="fw-bold mb-0">128</p><p class="small text-body-secondary mb-0">Routes</p></div>
      <div class="col border-start"><p class="fw-bold mb-0">4.9k</p><p class="small text-body-secondary mb-0">Followers</p></div>
      <div class="col border-start"><p class="fw-bold mb-0">36</p><p class="small text-body-secondary mb-0">Trips</p></div>
    </div>
    <div class="d-flex gap-2">
      <button type="button" class="btn bcd-follow flex-grow-1 py-2" id="bcd-follow" data-bs-toggle="button" aria-pressed="false">Follow</button>
      <a href="#" class="btn btn-outline-secondary bcd-icon-btn" aria-label="Message Mara"><i class="bi bi-chat-dots" aria-hidden="true"></i></a>
      <a href="#" class="btn btn-outline-secondary bcd-icon-btn" aria-label="Share profile"><i class="bi bi-share" aria-hidden="true"></i></a>
    </div>
  </div>
</div>

Testimonial cards

  • figure
  • blockquote
  • figcaption
  • visually-hidden

Each card wraps a figure with a blockquote and figcaption, the semantic pattern from the Bootstrap typography docs. Star icons are hidden from screen readers and replaced by visually-hidden text, and initials avatars are plain flex boxes with subtle background utilities.

View code
<div class="bcd-quotes w-100">
  <div class="row row-cols-1 row-cols-md-2 g-4">
    <div class="col">
      <figure class="card h-100 border-0 shadow-sm mb-0">
        <div class="card-body p-4">
          <div class="text-warning-emphasis mb-3 small">
            <i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i>
            <span class="visually-hidden">5 out of 5 stars</span>
          </div>
          <blockquote class="mb-0">
            <p class="bcd-quote-text">We moved our client portal to the new card layout in a weekend. Support tickets about finding invoices dropped to almost zero.</p>
          </blockquote>
        </div>
        <figcaption class="card-footer bg-transparent border-0 px-4 pb-4 pt-0 d-flex align-items-center gap-3">
          <span class="bcd-avatar bg-primary-subtle text-primary-emphasis">DK</span>
          <span><span class="d-block fw-semibold">Dana Kowalski</span><span class="small text-body-secondary">Head of Ops, Northpine Legal</span></span>
        </figcaption>
      </figure>
    </div>
    <div class="col">
      <figure class="card h-100 border-0 shadow-sm mb-0">
        <div class="card-body p-4">
          <div class="text-warning-emphasis mb-3 small">
            <i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star-fill" aria-hidden="true"></i><i class="bi bi-star" aria-hidden="true"></i>
            <span class="visually-hidden">4 out of 5 stars</span>
          </div>
          <blockquote class="mb-0">
            <p class="bcd-quote-text">Clean, fast and easy to theme. I wish the export had more options, but the team picked it up without training.</p>
          </blockquote>
        </div>
        <figcaption class="card-footer bg-transparent border-0 px-4 pb-4 pt-0 d-flex align-items-center gap-3">
          <span class="bcd-avatar bg-success-subtle text-success-emphasis">RO</span>
          <span><span class="d-block fw-semibold">Rafael Osei</span><span class="small text-body-secondary">Designer, Kiln and Co.</span></span>
        </figcaption>
      </figure>
    </div>
  </div>
</div>

Event card with a date badge

  • position-absolute
  • card-img-top
  • icon-link
  • text-bg-*

The card is already position: relative, so a date badge built from a time element can sit on the image with position-absolute top-0 start-0. Meta lines use Bootstrap Icons, and the speaker list is a stack of overlapping initials made with negative margins.

View code
<div class="card bcd-event w-100 shadow-sm">
  <img src="city-skyline.webp" class="card-img-top bcd-event-img" alt="A city skyline at sunrise above the lakefront" width="800" height="600">
  <time class="bcd-event-date position-absolute top-0 start-0 m-3 bg-body rounded-3 text-center shadow-sm" datetime="2026-11-14T18:30">
    <span class="d-block small fw-bold text-danger text-uppercase">Nov</span>
    <span class="d-block fs-4 fw-bold lh-1">14</span>
  </time>
  <div class="card-body">
    <span class="badge text-bg-light border mb-2">Meetup</span>
    <h3 class="card-title h5">Frontend Night: container queries in production</h3>
    <ul class="list-unstyled small text-body-secondary mb-3">
      <li class="mb-1"><i class="bi bi-clock me-2" aria-hidden="true"></i>Fri, 18:30 to 21:00</li>
      <li><i class="bi bi-geo-alt me-2" aria-hidden="true"></i>Harbor Works, 3rd floor</li>
    </ul>
    <div class="d-flex flex-wrap align-items-center justify-content-between gap-2">
      <div class="d-flex align-items-center text-nowrap">
        <span class="bcd-event-face bg-primary-subtle text-primary-emphasis">AL</span>
        <span class="bcd-event-face bg-warning-subtle text-warning-emphasis">JT</span>
        <span class="bcd-event-face bg-success-subtle text-success-emphasis">MB</span>
        <span class="small text-body-secondary ms-2">86 going</span>
      </div>
      <a href="#" class="btn btn-danger px-3 py-2 text-nowrap">Reserve a seat</a>
    </div>
  </div>
</div>

Image overlay with readable text

  • card-img-overlay
  • card-img
  • bg-opacity-75
  • text-bg-dark

card-img turns the photo into the card background and card-img-overlay places the content on top. Instead of trusting the photo for contrast, the text sits in a solid dark panel with bg-dark bg-opacity-75 pinned to the bottom with flex utilities, so it stays readable over bright sky.

View code
<div class="card text-bg-dark border-0 bcd-overlay w-100 shadow">
  <img src="coast-road.webp" class="card-img bcd-overlay-img" alt="Terraced green cliffs dropping to a calm sea" width="800" height="600">
  <div class="card-img-overlay d-flex flex-column justify-content-end p-3">
    <div class="bg-dark bg-opacity-75 rounded-3 p-3">
      <p class="small text-uppercase fw-semibold mb-1 opacity-75">Guide</p>
      <h3 class="card-title h4 mb-2">The cliff road, end to end</h3>
      <p class="card-text small mb-0">Twelve viewpoints, two village stops and the best hour to drive it.</p>
    </div>
  </div>
</div>

Tabs, color modes and CSS variables

Header tabs wired to the tab plugin, a card set in dark mode, a card restyled only through --bs-card-* variables and a loading state built from placeholders.

Card with header nav tabs

  • card-header-tabs
  • data-bs-toggle="tab"
  • tab-content
  • --bs-nav-tabs-*

Click the Usage tab.

A nav-tabs list with card-header-tabs sits inside card-header, and the tab plugin (data-bs-toggle="tab") switches the panes in card-body with the right roles and aria-selected. The tab colors come from --bs-nav-link-* and --bs-nav-tabs-* variables on a prefixed class.

View code
<div class="card bcd-tabs w-100 shadow-sm">
  <div class="card-header">
    <ul class="nav nav-tabs card-header-tabs" role="tablist">
      <li class="nav-item" role="presentation">
        <button class="nav-link active" id="bcd-tab-overview" data-bs-toggle="tab" data-bs-target="#bcd-pane-overview" type="button" role="tab" aria-controls="bcd-pane-overview" aria-selected="true">Overview</button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link" id="bcd-tab-usage" data-bs-toggle="tab" data-bs-target="#bcd-pane-usage" type="button" role="tab" aria-controls="bcd-pane-usage" aria-selected="false">Usage</button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link" id="bcd-tab-keys" data-bs-toggle="tab" data-bs-target="#bcd-pane-keys" type="button" role="tab" aria-controls="bcd-pane-keys" aria-selected="false">Keys</button>
      </li>
    </ul>
  </div>
  <div class="card-body tab-content p-4">
    <div class="tab-pane fade show active" id="bcd-pane-overview" role="tabpanel" aria-labelledby="bcd-tab-overview" tabindex="0">
      <h3 class="card-title h5">Atlas Maps API</h3>
      <p class="card-text text-body-secondary">Geocoding and routing for the Harbor Works app. Production project, created March 2026.</p>
      <span class="badge text-bg-success">Healthy</span> <span class="small text-body-secondary ms-2">99.98% uptime, last 30 days</span>
    </div>
    <div class="tab-pane fade" id="bcd-pane-usage" role="tabpanel" aria-labelledby="bcd-tab-usage" tabindex="0">
      <h3 class="card-title h5">Monthly requests</h3>
      <p class="card-text text-body-secondary mb-2">182,400 of 250,000 included requests used.</p>
      <div class="progress mb-2" role="progressbar" aria-label="Requests used" aria-valuenow="73" aria-valuemin="0" aria-valuemax="100">
        <div class="progress-bar bcd-tabs-bar" style="width: 73%">73%</div>
      </div>
      <p class="small text-body-secondary mb-0">Resets on November 1.</p>
    </div>
    <div class="tab-pane fade" id="bcd-pane-keys" role="tabpanel" aria-labelledby="bcd-tab-keys" tabindex="0">
      <h3 class="card-title h5">API keys</h3>
      <p class="card-text text-body-secondary">Two active keys. Rotate keys every 90 days.</p>
      <a href="#" class="btn btn-outline-dark py-2">Manage keys</a>
    </div>
  </div>
</div>

Dark mode status cards

  • data-bs-theme="dark"
  • border-*-subtle
  • text-*-emphasis
  • card-header

The stage sets data-bs-theme="dark" on the html element, and the cards adapt with no extra CSS because .card reads its colors from the theme variables. The status accents use border-*-subtle and text-*-emphasis, which Bootstrap 5.3 retunes for each color mode. The same markup works on one card if you put data-bs-theme="dark" on it.

View code
<div class="bcd-status w-100">
  <div class="row row-cols-1 row-cols-md-3 g-3">
    <div class="col">
      <div class="card h-100 border-success-subtle">
        <div class="card-header d-flex align-items-center gap-2 text-success-emphasis">
          <i class="bi bi-check-circle-fill" aria-hidden="true"></i><span class="fw-semibold">Production</span>
        </div>
        <div class="card-body">
          <p class="card-text mb-1 font-monospace small">main @ 4f2c9a1</p>
          <p class="card-text small text-body-secondary mb-0">Deployed 12 minutes ago by Ines</p>
        </div>
        <div class="card-footer small text-body-secondary">Build 2m 41s</div>
      </div>
    </div>
    <div class="col">
      <div class="card h-100 border-warning-subtle">
        <div class="card-header d-flex align-items-center gap-2 text-warning-emphasis">
          <i class="bi bi-hourglass-split" aria-hidden="true"></i><span class="fw-semibold">Staging</span>
        </div>
        <div class="card-body">
          <p class="card-text mb-1 font-monospace small">feat/search @ b81e07d</p>
          <p class="card-text small text-body-secondary mb-0">Waiting for review, 2 checks left</p>
        </div>
        <div class="card-footer small text-body-secondary">Queued 4 minutes ago</div>
      </div>
    </div>
    <div class="col">
      <div class="card h-100 border-danger-subtle">
        <div class="card-header d-flex align-items-center gap-2 text-danger-emphasis">
          <i class="bi bi-x-octagon-fill" aria-hidden="true"></i><span class="fw-semibold">Preview</span>
        </div>
        <div class="card-body">
          <p class="card-text mb-1 font-monospace small">fix/cart-total @ 09ad3c4</p>
          <p class="card-text small text-body-secondary mb-0">Failed: 3 unit tests</p>
        </div>
        <div class="card-footer d-flex justify-content-between align-items-center">
          <span class="small text-body-secondary">1 hour ago</span>
          <a href="#" class="btn btn-sm btn-outline-danger">View log</a>
        </div>
      </div>
    </div>
  </div>
</div>

Card themed with --bs-card-* variables

  • --bs-card-bg
  • --bs-card-border-radius
  • --bs-card-cap-bg
  • --bs-card-spacer-*

The same card markup as the docs, restyled only by setting --bs-card-* variables on a prefixed class: background, border color and radius, cap background, spacing, title color and shadow. Nothing targets .card directly, so every other card on the site keeps its defaults.

View code
<div class="card bcd-paper w-100">
  <div class="card-header d-flex justify-content-between align-items-center">
    <span class="fw-semibold">Field notes</span>
    <span class="small">Issue 14</span>
  </div>
  <img src="jellyfish.webp" class="bcd-paper-img" alt="An orange sea nettle jellyfish trailing long tentacles in blue water" width="800" height="600">
  <div class="card-body">
    <h3 class="card-title h4">Why sea nettles bloom in late summer</h3>
    <p class="card-text">Warm, low-oxygen water favors jellyfish over the fish that eat them. A marine biologist walks us through a season of counts from the harbor wall.</p>
    <a href="#" class="card-link fw-semibold">Read the issue</a>
    <a href="#" class="card-link">Subscribe</a>
  </div>
  <div class="card-footer small">8 min read, illustrated</div>
</div>

Loading state with placeholders

  • placeholder
  • placeholder-glow
  • aria-busy
  • hidden

Click Refresh to see the skeleton.

Two copies of the card body: real content and a skeleton made of placeholder spans with col-* widths inside placeholder-glow. The Refresh button hides one and shows the other for a moment and sets aria-busy on the card, so the loading state is not only visual.

View code
<div class="card bcd-load w-100 shadow-sm" id="bcd-load" aria-busy="false">
  <div class="card-header bg-body d-flex justify-content-between align-items-center">
    <h3 class="h6 mb-0">Weather at the trailhead</h3>
    <button type="button" class="btn btn-sm btn-outline-secondary px-3 py-2" id="bcd-load-btn"><i class="bi bi-arrow-clockwise me-1" aria-hidden="true"></i>Refresh</button>
  </div>
  <div class="card-body" id="bcd-load-real">
    <div class="d-flex align-items-center gap-3 mb-3">
      <i class="bi bi-cloud-sun fs-1 text-warning-emphasis" aria-hidden="true"></i>
      <div><p class="h3 mb-0">8&deg;C</p><p class="small text-body-secondary mb-0">Partly cloudy, wind 14 km/h</p></div>
    </div>
    <p class="card-text small mb-0">Trail conditions: dry below 1,200 m, patchy snow above the second hut.</p>
  </div>
  <div class="card-body placeholder-glow" id="bcd-load-skeleton" aria-hidden="true" hidden>
    <div class="d-flex align-items-center gap-3 mb-3">
      <span class="placeholder rounded-circle bcd-load-dot"></span>
      <div class="flex-grow-1"><span class="placeholder col-4 placeholder-lg d-block mb-2"></span><span class="placeholder col-8 d-block"></span></div>
    </div>
    <span class="placeholder col-12"></span>
    <span class="placeholder col-7"></span>
  </div>
</div>

What Are Bootstrap Cards?

The card is Bootstrap’s general-purpose content box. One bordered container holds an image, text, list items, links and a footer, and because the component runs on flexbox, alignment and stacking work without extra CSS.

According to the official documentation, a card in Bootstrap 5.3.8 sets no width, so it fills its parent, and sets no margin, so spacing utilities handle the gaps. It is also position: relative, which is what makes stretched links work.

Cards pair easily with other Bootstrap components. A list group or a button goes straight into a card with no wrapper tricks.

Bootstrap 3 had panels and wells for this job, plus thumbnails on the side. Cards replaced all of them, and the old looks survive as modifier classes (Bootstrap 5.3 documentation).

Bootstrap’s version of the card layout pattern gives each item its own box with a clear edge.

A card holds content. It does not place itself on the page, because rows, columns and card groups do that.

Card Markup and the Classes That Build It

Only two classes are required, card on the wrapper and card-body on the padded content inside it. Titles, text, images, headers and footers each get their own class, but you can skip any of them.

Building a basic card

  1. Create a div with the class card.
  2. Add an img with card-img-top at the top of the wrapper if the card needs a picture, and write real alt text for it.
  3. Add a div with the class card-body below the image.
  4. Inside the body, place a heading with card-title and a paragraph with card-text.
  5. Close with a button, or with a link that carries card-link.

The result is plain HTML with class names, so a card needs no JavaScript.

Titles, subtitles and links

card-title goes on any heading tag, and the docs examples use h5. The class sets the look, so pick whichever heading level the page outline needs.

card-subtitle sits under the title inside card-body and lines up with it. The docs add mb-2 and text-body-secondary to it.

Anchor tags get card-link, and several of them can sit side by side.

Headers and footers

card-header and card-footer are direct children of the card, outside the body. Put them inside the body and they sit within its padding instead of spanning the full width.

A heading tag can carry card-header on its own, so an h5 with that class works as a header without a wrapper div.

Content Types Inside a Card: List Groups, Nav Tabs, Links and Buttons

Most of what you would want inside a card (list groups, navs, links, buttons) drops in directly, provided it goes in the right slot.

List groups

Add list-group and list-group-flush to a ul, and the list becomes its own block inside the card. It can stand alone without a card-body. It also works under a card-header or above a card-footer.

The kitchen sink example in the docs goes further. A flush list group sits between two card-body blocks, with an image above and links below.

Navigation in the header

For tabs, put a ul with nav, nav-tabs and card-header-tabs inside card-header. Pills use the same structure with nav-pills and card-header-pills.

The card example shows markup only. Switching content between panes takes the tab plugin, and this walkthrough of Bootstrap tabs covers that part.

Links and clickable cards

Links on a card come in a few forms, and they differ in how much of the card responds to a click.

ElementAppearanceClick area
card-linkPlain text linkThe link text
btn btn-primaryFilled buttonThe button
Either, plus stretched-linkUnchangedThe whole card

Button variants such as btn-primary are covered in this guide to Bootstrap buttons.

stretched-link works through a ::after pseudo-element that covers the nearest positioned ancestor. Cards are position: relative already, so the class needs no extra HTML.

It stops working in a couple of cases. Bootstrap’s stretched link page recommends against multiple links and tap targets in one stretched block. An inner element with a position other than static, a transform, a perspective or, in Firefox, a filter also becomes the new containing block, which shrinks the clickable area to that element.

Image Placement: Top, Bottom, Overlay and Horizontal Cards

Images can sit on top of a card, at the bottom, behind the text as an overlay, or beside it in a horizontal layout.

Top and bottom image caps

card-img-top and card-img-bottom round the matching corners to fit the card’s border radius. For a top cap the img comes first inside the card. For a bottom cap it goes after the card-body.

Image overlays

Put card-img on the image to turn it into the card background, then place the text in a div with card-img-overlay. The docs example adds text-bg-dark to the card, which gives light text on a dark image.

Overlay content must not be taller than the image, or it spills outside the picture.

Text sits on whatever pixels the photo holds. Test its color contrast against the brightest part of the image, not the average.

Horizontal cards

Start with a card that has a max-width, set to 540px in the docs example. Inside it goes a row with row g-0, which removes the gutters. The image column is col-md-4 and holds an img with img-fluid and rounded-start, and the text column is col-md-8 and holds the card-body.

Below the md breakpoint (768px) the columns stack, so the image sits above the text on phones. The docs warn that further adjustments may be needed depending on the content.

Bootstrap 5 also dropped the media object component, since flexbox utilities can recreate it. The horizontal card above uses the grid instead, a pattern that already existed in Bootstrap 4.

How to Size Cards and Arrange Them in a Responsive Grid

A card is as wide as its parent until you set a width, and the grid decides how many cards share a row.

Sizing a single card

MethodExampleUse when
Grid columnscol-sm-6 around the cardThe card belongs to a page grid
Sizing utilitiesw-75, w-50A quick percentage width is enough
Custom CSSwidth: 18remThe width must stay fixed

When the page already runs on the Bootstrap grid system, put the card inside a col-* column and let the column set the width.

The docs example adds mb-3 mb-sm-0 to the first column. That gives the cards a gap when stacked and none once they sit side by side.

Rows of cards with row-cols and gutters

row-cols-1 row-cols-md-2 g-4 on the row gives one card per row on small screens and two from the md breakpoint up.

Bootstrap breakpoints are built on min-width media queries, so a md class applies at 768px and at every wider size.

Wrap each card in a div with the class col, and set the card count per breakpoint on the row. With row-cols-3 and four cards, the fourth wraps to a new line. $grid-row-columns defaults to 6, and any row-cols value above that limit is ignored.

The default gutter is 1.5rem, and g-4 produces the same value. Use gx-* for horizontal gaps, gy-* for vertical ones and g-0 to remove both.

Equal height with h-100

The columns in a row stretch to equal height, but the card inside each column only grows when told to. Add h-100 to every card and a short card matches its taller neighbors.

Footers line up automatically once h-100 is on the cards. To make equal height the default across a project, set $card-height to 100% in Sass (Bootstrap 5.3 documentation).

Card Group, Row-Cols or Masonry: Which Layout Fits?

Pick card-group for a joined strip of a few cards, row-cols with h-100 for separate cards in a grid, and Masonry when card heights differ too much for equal rows.

LayoutLookHeightBreaks when
card-groupOne attached stripEqual by defaultYou need gaps between cards
row-cols with h-100Separate cards with guttersEqual per rowh-100 is missing, so heights follow content
Masonry pluginUneven columns packed tightNatural per cardThe script fails to load, or h-100 forces equal heights

If the content lengths are similar and you want visible gaps, row-cols with h-100 is the safe pick. Two to four cards that should read as one unit suit card-group. Quotes, photos and text cards mixed into one grid is what Masonry is for, and I would not bring it in for anything less.

card-group starts stacked and switches to display: flex at the sm breakpoint (576px), per the Bootstrap 5.3 docs. Footers line up across the attached cards without extra classes.

Masonry is not bundled with Bootstrap. The official 5.3 example loads masonry-layout 4.2.2 from the jsDelivr CDN and adds a data-masonry attribute with percentPosition set to true on the row.

Leave h-100 off in a Masonry grid. Equal heights cancel the reason to use it.

Bootstrap 4 had a CSS-only answer called card-columns. It filled cards down each column instead of across rows, and it needed display: inline-block to stop cards splitting between columns (Bootstrap 4.6 documentation).

Teams that would rather skip the plugin can look at CSS-only masonry layouts, though Bootstrap does not ship one.

One oddity in the docs: the 5.3 card page still says these layout options are not yet responsive, and the same sentence appears in the 4.6 docs. The examples right below it do respond, since card-group attaches from sm and row-cols-md-* applies from md.

Styling Bootstrap Cards With Utilities and CSS Variables

Utilities handle one-off changes to a single card. The –bs-card-* CSS variables are the better route when every card should change.

Utilities for color and borders

text-bg-{color} sets a background and a contrasting text color in one class. Bootstrap added it in v5.2.0, and the older pairing of text-* and bg-* still works.

border-{color} changes the border color only. Add a text-{color} class to the card or its body if the text should match.

Headers and footers take their own utilities. bg-transparent removes the cap tint, and a border-{color} class recolors its edge (Bootstrap 5.3 documentation).

CSS variables on the card

Since v5.2.0, every card carries local variables set on .card, so one class can restyle a single card or all of them.

GoalVariable
Rounder corners–bs-card-border-radius
Card shadow–bs-card-box-shadow, plus box-shadow: var(–bs-card-box-shadow) unless $enable-shadows is on
Tinted header and footer–bs-card-cap-bg
Roomier body padding–bs-card-spacer-x and –bs-card-spacer-y

Set the variable on a custom class such as product-card, not on .card itself. The rest of the site keeps the defaults.

The shadow variable needs that extra declaration because the card only applies it when $enable-shadows is true in Sass, and the default is false. For a custom shadow value, a box shadow generator saves guesswork before you paste the result into –bs-card-box-shadow.

Bootstrap 5.0 and 5.1 ignore all of these overrides, because the card variables arrived with 5.2.0.

Dark mode works per card too. Setting data-bs-theme=dark on one card flips its palette and leaves the page alone, and cards have carried an explicit text color since 5.3.0-alpha2 so they render correctly across modes.

When Sass is the better route

CSS variables suit projects that load compiled Bootstrap from a CDN, since they need no build step.

Projects that compile Bootstrap anyway can change the Sass variables once, because the docs state that the CSS variable values are set via Sass. The relevant ones are $card-border-radius and $card-spacer-x, plus $card-bg for the background.

Overriding Bootstrap CSS from a separate stylesheet is the fallback when neither route fits, and overriding Bootstrap CSS safely comes down to load order and selector specificity.

Bootstrap Cards in Versions 3, 4 and 5

Cards exist in Bootstrap 4 and 5 only, and the card component itself changed less between them than the layout classes around it did.

VersionMulti-card layoutMasonry-style columnsStatus
Bootstrap 3NoneNoneFinal release 3.4.1, Feb 13, 2019
Bootstrap 4card-group, card-deck, row-colscard-columns, CSS onlyEnd of life since Jan 1, 2023, final release 4.6.2, Jul 19, 2022
Bootstrap 5card-group, row-colsMasonry plugin, not bundledActive, 5.3.8 from Aug 26, 2025

Release dates come from the twbs/bootstrap GitHub releases, and the end-of-life status from Bootstrap’s own release support table. As of October 2026, the Bootstrap docs list 5.3.8 as the current release.

Moving a card layout from Bootstrap 4 to 5 mostly means swapping classes, according to the official migration guide and the two versions of the card docs.

  1. Replace card-deck with a row that carries row-cols-*, and wrap each card in a col.
  2. Move the gap from mb-4 on each col to a g-* class on the row. The 4.6 grid example uses col mb-4, the 5.3 one uses g-4.
  3. Swap card-columns for the Masonry plugin.
  4. Rename no-gutters to g-0 on horizontal cards, and text-right to text-end.
  5. Replace text-white bg-* pairs with text-bg-*, and text-muted with text-body-secondary (deprecated in 5.3, removal planned for v6).
  6. Replace any card-based accordion with the Accordion component.

Gutters also shrank. They went from 30px in Bootstrap 4 to 1.5rem (about 24px) in Bootstrap 5, so a ported grid looks slightly tighter.

Sources disagree on the card-deck replacement. Some migration write-ups pair card-deck with card-group, while Bootstrap’s own guide points to the grid.

The two are not interchangeable. The 4.6 docs describe card-deck as cards that are not attached to one another, and card-group attaches them, so swapping in card-group changes the look. Row-cols with gutters keeps the gaps.

When Bootstrap Cards Break or Do Not Fit

Cards run into trouble with low-contrast color variants, loading states that screen readers cannot follow, projects that still need Internet Explorer, and content that another component handles better.

Contrast and color-only meaning

Bootstrap’s accessibility page warns that some default palette combinations fall below the WCAG 2.2 minimums, which are 4.5:1 for text and 3:1 for non-text elements, especially on light backgrounds.

Test every text-bg-* variant you ship, not just the first one. Check the text against its background, then the border against the page. Put the meaning in visible text, because a red border tells a screen reader nothing, and use visually-hidden for a label the design cannot show.

Loading states with placeholders

Bootstrap added placeholders in v5.1, and they work as skeleton screens built from HTML and CSS alone.

To build one, copy the card markup and replace the text with spans that carry placeholder and a col-* width. Add placeholder-glow or placeholder-wave to animate them.

The docs put aria-hidden on the loading card. That hides it from screen readers but announces nothing, so telling users the real content arrived takes extra ARIA attributes and some JavaScript.

Cases where another approach fits better

Internet Explorer 10 and 11 are out, since Bootstrap 5 dropped both. Bootstrap 4 is the last line that supports them, and it reached end of life on January 1, 2023, so it gets no further updates or security patches from the Bootstrap team.

Expand and collapse is another miss. The card-based accordion was replaced by the Accordion component in v5.0, so a card is the wrong tool for toggling content.

A card that holds only a list is overkill too. A list group alone does the same job with less markup.

Custom breakpoint logic is the last one. The Bootstrap docs state that CSS variables do not work in media queries, so a card layout that depends on custom breakpoints needs Sass, not –bs-* variables.

A card holds one self-contained item. When the content is a list, a toggle or a status, a different component fits better.

Bootstrap Cards FAQ

How do I center text in Bootstrap cards?

Add text-center to the card to center every line inside it, or to card-body, card-title or card-text to center one part.

text-end aligns right and text-start aligns left, according to the Bootstrap 5.3 docs.

Can Bootstrap cards have a hover effect?

Yes, but through custom CSS, because the card docs list no hover class. Shadow utilities such as shadow-sm are fixed values and do not react to the pointer.

Put a transition on the card and change its box-shadow or a transform on :hover. Wrap the movement in a prefers-reduced-motion: no-preference media query. These CSS card hover effects show common patterns.

Which CDN link loads Bootstrap for testing card code?

Add the jsDelivr stylesheet for 5.3.8 to the head, at https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css. Keep its integrity attribute and update the hash with each version.

Cards need only the CSS. The JavaScript bundle matters for plugin parts, such as the Tab plugin behind header nav tabs.

Does React-Bootstrap have a Card component?

Yes. React-Bootstrap exports Card with subcomponents such as Card.Body, Card.Title, Card.Text, Card.Img, Card.Header and Card.Footer, and its current docs target Bootstrap 5.

Grids use Row props like xs={1} md={2} with a g-4 class, and each Card sits inside a Col, since card-deck no longer exists.

Where can I find free templates for Bootstrap cards?

Start with the official Bootstrap Examples page. It includes a Pricing example built with cards and an Album template for galleries and portfolios.

Both ship in the 5.3.8 examples download, and Bootstrap’s code is MIT licensed.

The Card Pattern Worth Building First

Bootstrap cards work best when a project starts with one pattern, a row with row-cols-1 row-cols-md-3 g-4, a col around each card, and h-100 on every card.

Grid and equal heights come first, then images, footers and links. Styling through –bs-card-* variables goes last.

Layout leads because images, footers and clickable areas all change card height, and h-100 shows right away whether the row still lines up. Styling can wait because a single custom class changes it without touching the markup.

The cost of h-100 is aligned rows with some empty space inside the shorter cards. I think that is a fair price.

This advice was checked against Bootstrap 5.3.8 in October 2026. The 5.3 migration guide states that CSS variable usage stays incomplete until Bootstrap 6, so the –bs-card-* step is the one to recheck when that release ships.