Bootstrap 5 Tables: Responsive, Striped, and Sortable Patterns

Bootstrap 5 Tables: Responsive, Striped, and Sortable Patterns

bootstrap 5 tables, from the simplest markup to sortable columns wired up with vanilla JavaScript.

Key Takeaways

  • Bootstrap 5’s .table base class applies consistent spacing and borders with no extra CSS required.
  • Wrapping a table in .table-responsive enables horizontal scrolling on small viewports without breaking your layout.
  • Striped, bordered, hover, and colour variant classes can be combined freely using Bootstrap’s utility system.
  • Sortable tables are achievable with a small vanilla JavaScript function, no jQuery plugin needed.
  • Accessibility requires <thead>, <th scope>, and aria-sort attributes for screen reader compatibility.
  • CSS custom properties like --cnvs-themecolor make it straightforward to match table accents to your brand palette.

The Base Table Markup You Should Always Start With

Bootstrap 5 requires only the .table class on a <table> element to apply its default styles. That single class gives you consistent cell padding, a bottom border on each row, and text colour inherited from the page’s base typography. No extra stylesheet, no component import beyond Bootstrap’s main CSS bundle.

Semantic structure matters, particularly for accessibility. Separate your header row into <thead>, your body rows into <tbody>, and optionally totals or summaries into <tfoot>. Use scope="col" on column headers and scope="row" on row headers. If your audience includes screen reader users, this is not optional. It also costs no more than a few extra characters.

<table class="table">
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">Plan</th>
      <th scope="col">Price</th>
      <th scope="col">Users</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">1</th>
      <td>Starter</td>
      <td>$9/mo</td>
      <td>5</td>
    </tr>
    <tr>
      <th scope="row">2</th>
      <td>Pro</td>
      <td>$29/mo</td>
      <td>25</td>
    </tr>
  </tbody>
</table>
A black and white photo of a living room
Photo by Annie Spratt on Unsplash

Striped, Bordered, and Hover: Combining Bootstrap Table Styles

Bootstrap 5 ships modifier classes that you stack on top of .table. Knowing what each one does, and when to combine them, prevents you from over-engineering a simple component.

  • .table-striped: applies a subtle background to alternating <tbody> rows using CSS nth-of-type. In Bootstrap 5.1+, .table-striped-columns applies the same pattern vertically instead.
  • .table-bordered: adds borders on all four sides of every cell, useful for dense data grids.
  • .table-borderless: removes all borders, better suited to card-embedded tables where the card itself provides containment.
  • .table-hover: highlights the row under the cursor, improving scannability in long tables.
  • .table-sm: halves cell padding, useful when you need to fit more data without triggering horizontal scroll.
<table class="table table-striped table-hover table-bordered">
  <thead class="table-dark">
    <tr>
      <th scope="col">Project</th>
      <th scope="col">Status</th>
      <th scope="col">Due Date</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Brand Redesign</td>
      <td><span class="badge bg-success">Complete</span></td>
      <td>Jan 15, 2025</td>
    </tr>
    <tr>
      <td>API Integration</td>
      <td><span class="badge bg-warning text-dark">In Progress</span></td>
      <td>Feb 28, 2025</td>
    </tr>
  </tbody>
</table>

Colour variants such as .table-primary, .table-danger, and .table-warning can be applied at the table, row, or individual cell level, giving you precise control over status highlighting without touching your stylesheet. The same layering principle used in Bootstrap 5’s utility class system applies here: compose classes rather than writing one-off overrides.

Making Tables Responsive With Bootstrap’s Wrapper Classes

A responsive table bootstrap implementation comes down to one wrapper div. Enclose your <table> inside a <div class="table-responsive"> and Bootstrap applies overflow-x: auto, enabling horizontal scrolling on viewports too narrow to show all columns. The table retains its full internal width. Nothing gets truncated or hidden.

<div class="table-responsive">
  <table class="table table-striped">
    <!-- wide table content -->
  </table>
</div>

If you only need responsive behaviour below a specific breakpoint, Bootstrap provides breakpoint-suffixed variants: .table-responsive-sm, .table-responsive-md, .table-responsive-lg, and .table-responsive-xl. These apply the overflow behaviour only when the viewport is narrower than the named breakpoint, leaving the table at its natural width on larger screens.

<!-- Scroll only on screens smaller than 768px -->
<div class="table-responsive-md">
  <table class="table">
    <!-- content -->
  </table>
</div>

For tables with many columns (analytics dashboards or financial reports, for instance), consider combining .table-sm with .table-responsive to reduce the point at which scrolling kicks in. If column priority matters, you can hide lower-priority columns on small screens using Bootstrap’s display utilities (d-none d-md-table-cell), keeping the most critical data visible without any scrolling at all.

a laptop computer sitting on top of a wooden desk
Photo by Igor Bumba on Unsplash

Custom Table Styling With CSS Variables

Bootstrap 5 exposes table-specific CSS custom properties that you can override without touching the source Sass. This is the fastest path to aligning a table’s appearance with your brand or template theme.

<style>
  .table {
    --bs-table-striped-bg: rgba(var(--bs-primary-rgb), 0.06);
    --bs-table-hover-bg: rgba(var(--bs-primary-rgb), 0.12);
    --bs-table-border-color: #e0e0e0;
  }

  / Using Canvas Template's theme colour variable /
  thead.table-brand {
    background-color: var(--cnvs-themecolor);
    color: #fff;
  }
</style>

<table class="table table-striped table-hover">
  <thead class="table-brand">
    <tr>
      <th scope="col">Feature</th>
      <th scope="col">Starter</th>
      <th scope="col">Pro</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Custom Domain</td>
      <td>No</td>
      <td>Yes</td>
    </tr>
  </tbody>
</table>

The --cnvs-themecolor variable is available globally in the Canvas HTML Template, making it straightforward to pull your primary brand colour into table headers, hover states, or border accents without duplicating hex values across your CSS. For a deeper look at theming with variables, see the guide on using SCSS variables to theme a Bootstrap 5 site.

Building Sortable Tables With Vanilla JavaScript

Sortable tables improve usability considerably on data-heavy interfaces. You do not need a plugin for basic column sorting. A small vanilla JavaScript function handles the majority of real-world cases.

The approach: attach click listeners to <th> elements, read the column index, collect all <tr> elements from <tbody>, sort them by cell text content, then re-insert them. Toggle an aria-sort attribute to communicate sort direction to assistive technologies.

<table class="table table-hover" id="sortable-table">
  <thead>
    <tr>
      <th scope="col" data-sortable="true" aria-sort="none" style="cursor:pointer">Name</th>
      <th scope="col" data-sortable="true" aria-sort="none" style="cursor:pointer">Revenue</th>
      <th scope="col" data-sortable="true" aria-sort="none" style="cursor:pointer">Region</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Acme Corp</td><td>84000</td><td>North</td></tr>
    <tr><td>Globex</td><td>120000</td><td>East</td></tr>
    <tr><td>Initech</td><td>47500</td><td>South</td></tr>
  </tbody>
</table>

<script>
  document.querySelectorAll('#sortable-table th[data-sortable]').forEach(function(header) {
    header.addEventListener('click', function() {
      const table = header.closest('table');
      const tbody = table.querySelector('tbody');
      const index = Array.from(header.parentElement.children).indexOf(header);
      const ascending = header.getAttribute('aria-sort') !== 'ascending';

      Array.from(tbody.querySelectorAll('tr'))
        .sort(function(a, b) {
          const aText = a.children[index].textContent.trim();
          const bText = b.children[index].textContent.trim();
          const aVal = isNaN(aText) ? aText : parseFloat(aText);
          const bVal = isNaN(bText) ? bText : parseFloat(bText);
          return ascending ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1);
        })
        .forEach(function(row) { tbody.appendChild(row); });

      table.querySelectorAll('th').forEach(function(th) {
        th.setAttribute('aria-sort', 'none');
      });
      header.setAttribute('aria-sort', ascending ? 'ascending' : 'descending');
    });
  });
</script>

This function handles both string and numeric sorting by detecting whether the cell value is a valid number. The aria-sort toggle communicates sort state to assistive technologies. It is a small addition, but an important one for Bootstrap 5 accessibility compliance.

Placing Tables Inside Cards and Page Layouts

Tables rarely live in isolation. In most production interfaces they sit inside Bootstrap 5 card components, dashboard panels, or modal dialogs. When embedding a table in a card, handle .card-body padding deliberately. If you want the table to extend edge-to-edge within the card, skip the .card-body wrapper and place the table directly inside .card, relying on Bootstrap’s built-in .card > .table style rules that remove the outer border and adjust the corner radius.

<div class="card">
  <div class="card-header">
    <strong>Q1 Sales Report</strong>
  </div>
  <div class="table-responsive">
    <table class="table table-striped mb-0">
      <thead>
        <tr>
          <th scope="col">Month</th>
          <th scope="col">Sales</th>
          <th scope="col">Growth</th>
        </tr>
      </thead>
      <tbody>
        <tr><td>January</td><td>$42,000</td><td class="text-success">+12%</td></tr>
        <tr><td>February</td><td>$38,500</td><td class="text-danger">-8%</td></tr>
        <tr><td>March</td><td><strong>$51,200</strong></td><td class="text-success">+33%</td></tr>
      </tbody>
    </table>
  </div>
</div>

Adding .mb-0 to the table removes Bootstrap’s default bottom margin, preventing a gap between the last row and the card’s bottom edge. Wrap the table (not the entire card) in .table-responsive to keep the card header and footer stationary while only the table body scrolls horizontally.

Table Best Practices for Production Projects

A few decisions made at the markup level prevent the most common table-related bugs and accessibility failures:

  1. Always use <thead> and <tbody>. Browsers and screen readers rely on this structure for correct rendering and navigation.
  2. Add scope attributes to all <th> elements. Use scope="col" for column headers and scope="row" for row headers.
  3. Use .mb-0 inside card containers to eliminate unwanted spacing at the table’s bottom edge.
  4. Prefer .table-responsive-{breakpoint} over the generic wrapper when the table is readable on tablet-sized viewports. Unnecessary scroll chrome on mid-range screens harms usability.
  5. Avoid inline widths on <td> or <th> unless you have a specific reason. Let Bootstrap’s box model and content determine column widths unless equal or fixed columns are a design requirement.
  6. Apply colour variants at the row level for status highlighting rather than re-styling entire tables. It is easier to maintain and more semantically clear.

FAQ

Wrap the <table> element in a <div class="table-responsive">. Bootstrap applies overflow-x: auto to this container, allowing the table to scroll horizontally on narrow viewports while maintaining its full column width internally. Use .table-responsive-md or similar breakpoint variants if you only want scroll behaviour below a specific screen size.

Yes. Bootstrap 5 table modifier classes are designed to be combined freely. You can stack .table-striped, .table-bordered, .table-hover, and colour variants like .table-dark on a single <table> element. The classes target distinct CSS properties and do not conflict with each other.

Applying .table-dark to the <table> element makes the entire table dark: headers, body rows, and footer. Applying it only to <thead> gives you a dark header row with a light body, which is a common pattern in dashboard interfaces. You can also use .table-light on <thead> for a subtle grey header against a white body.

No. A lightweight vanilla JavaScript function, roughly 20 lines, handles alphanumeric and numeric column sorting without any third-party dependency. For advanced requirements like server-side pagination, complex filtering, or virtual scrolling of thousands of rows, libraries such as DataTables or Tabulator are worth considering, but they add significant weight for simpler use cases.

Use the CSS custom property approach rather than hardcoding a hex value. In a Bootstrap 5 project using the Canvas HTML Template, you can reference var(--cnvs-themecolor) as the background colour on your <thead> element. Alternatively, override the Bootstrap CSS variable --bs-table-bg on a scoped selector to change the table’s base background without touching the global stylesheet.

Looking for a production-ready Bootstrap 5 HTML template? Browse Canvas Template demos and find the perfect starting point for your next project.

If you’re working with the Canvas HTML Template and want to generate production-ready layouts faster, try Canvas Builder free and see how much time you save on every project.

Skip the setup and build it free

Spin up a complete Bootstrap 5 site, blog included, with Canvas Builder. No coding, no cost.

Share:
Canvas Team
Canvas Team

Tutorials and tips for building beautiful Bootstrap 5 websites with the Canvas HTML Template and Canvas Builder.

More from the Canvas Blog