Lesson 9 of 12

Bootstrap Tables and List Groups

Tables and list groups both present a set of items, but they suit different content. A table is for data with columns that people compare, such as marks, timetables or orders. A list group is for a simple list of items or links, such as a settings menu or a list of messages. This lesson covers Bootstrap tables first, then list groups.

Bootstrap tables: the table class

Plain HTML tables have almost no styling. Add the table class to a <table> and you get comfortable padding, horizontal dividers and full width. Then add modifier classes for extra styles:

ClassEffect
table-stripedAlternate row shading, which helps eyes follow a long row
table-striped-columnsAlternate column shading
table-hoverHighlights the row under the mouse pointer
table-bordered / table-borderlessBorders on all cells, or none
table-smHalves the cell padding for dense data
table-primary, table-success, table-danger…Colours a table, a row or a single cell
table-activeMarks a selected row or cell
table-light / table-dark on <thead>A light or dark header row
table-group-dividerA thicker line between table sections, e.g. on <tbody>
align-middleVertically centres cell content
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A striped, hoverable table</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container py-4">
  <div class="table-responsive">
    <table class="table table-striped table-hover align-middle">
      <caption>Class 10 mid-term marks, Section B</caption>
      <thead class="table-light">
        <tr>
          <th scope="col">Roll no.</th>
          <th scope="col">Student</th>
          <th scope="col">Tamil</th>
          <th scope="col">English</th>
          <th scope="col">Maths</th>
          <th scope="col">Result</th>
        </tr>
      </thead>
      <tbody class="table-group-divider">
        <tr>
          <th scope="row">101</th><td>Aarthi S</td><td>92</td><td>88</td><td>95</td>
          <td><span class="badge text-bg-success">Pass</span></td>
        </tr>
        <tr>
          <th scope="row">102</th><td>Bala Murugan</td><td>76</td><td>81</td><td>69</td>
          <td><span class="badge text-bg-success">Pass</span></td>
        </tr>
        <tr class="table-danger">
          <th scope="row">103</th><td>Charan K</td><td>58</td><td>41</td><td>32</td>
          <td><span class="badge text-bg-danger">Re-test</span></td>
        </tr>
        <tr>
          <th scope="row">104</th><td>Divya Lakshmi</td><td>89</td><td>94</td><td>91</td>
          <td><span class="badge text-bg-success">Pass</span></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
</body>
</html>

Two accessibility details are built in here. The <caption> names the table, and scope="col" and scope="row" tell screen readers which header belongs to each cell. Bootstrap shows the caption below the table by default; add caption-top to the table to move it above. The red row uses a word (“Re-test”) as well as a colour, so the meaning does not depend on colour alone.

Responsive tables

Bootstrap tables with many columns do not fit on a phone. Wrapping the table in <div class="table-responsive"> lets it scroll sideways inside its box instead of stretching the whole page. Use a version with a breakpoint, such as table-responsive-md, to scroll only below that width. Scrolling is a fallback, though; for phone users, think about whether every column is needed. The HTML Tables lesson covers table markup in more depth.

Watch out: Bootstrap tables are for tabular data only. Do not use a table to arrange a page layout or a form. Use the grid or the flex utilities instead, which reflow on small screens and make sense to screen readers.

List groups

A list group turns a list into a neat, bordered stack. Put list-group on a <ul> or <ol> and list-group-item on each item. Useful variations:

  • active highlights the current item; add aria-current="true" too.
  • disabled greys out an item; add aria-disabled="true".
  • list-group-flush removes outer borders and rounded corners, ideal inside cards.
  • list-group-numbered on an <ol> adds numbers.
  • list-group-horizontal (or list-group-horizontal-md) lays items in a row.
  • list-group-item-success, list-group-item-warning and others colour individual items.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>List groups with badges and numbers</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container py-4">
  <div class="row g-4">
    <div class="col-md-6">
      <h2 class="h5">Inbox</h2>
      <ul class="list-group">
        <li class="list-group-item d-flex justify-content-between align-items-center">
          Project updates
          <span class="badge text-bg-primary rounded-pill">14</span>
        </li>
        <li class="list-group-item d-flex justify-content-between align-items-center">
          Design reviews
          <span class="badge text-bg-primary rounded-pill">2</span>
        </li>
        <li class="list-group-item list-group-item-warning d-flex justify-content-between align-items-center">
          Pending approvals
          <span class="badge text-bg-warning rounded-pill">1</span>
        </li>
      </ul>
    </div>
    <div class="col-md-6">
      <h2 class="h5">Steps to make filter coffee</h2>
      <ol class="list-group list-group-numbered">
        <li class="list-group-item">Add coffee powder to the top chamber</li>
        <li class="list-group-item">Pour hot water and wait for the decoction</li>
        <li class="list-group-item">Mix with boiled milk and sugar</li>
        <li class="list-group-item">Froth it between a tumbler and davara</li>
      </ol>
    </div>
  </div>
</div>
</body>
</html>

The badges are pushed to the right with the flex utilities d-flex justify-content-between align-items-center on each item. You will learn these properly in the flex lesson, but this combination appears so often that it is worth remembering now.

Clickable list groups

When the items are links or buttons, use <a> or <button> elements with list-group-item list-group-item-action. The action class adds hover and focus styles so the items feel clickable. Use a <div class="list-group"> as the wrapper here, because a list of buttons is not a <ul> without <li> elements.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A clickable settings menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container py-4" style="max-width: 420px;">
  <div class="list-group" id="settings">
    <button type="button" class="list-group-item list-group-item-action active" aria-current="true">Profile</button>
    <button type="button" class="list-group-item list-group-item-action">Notifications</button>
    <button type="button" class="list-group-item list-group-item-action">Privacy</button>
    <button type="button" class="list-group-item list-group-item-action" disabled>Billing (coming soon)</button>
  </div>
  <p class="mt-3" id="panel">Showing: Profile settings</p>
</div>
<script>
const items = document.querySelectorAll('#settings .list-group-item');
items.forEach((item) => {
  item.addEventListener('click', () => {
    items.forEach((other) => {
      other.classList.remove('active');
      other.removeAttribute('aria-current');
    });
    item.classList.add('active');
    item.setAttribute('aria-current', 'true');
    document.getElementById('panel').textContent = 'Showing: ' + item.textContent + ' settings';
  });
});
</script>
</body>
</html>

Table, list group or cards?

Choose by the shape of your content. If each item has the same few properties and people compare them across rows, such as marks, timings or dates, use a table. If each item is a single line or link, use a list group. If each item has an image, a description and its own action, cards are easier to scan. Picking the right one first saves you from fighting the component later, especially on small screens where wide tables struggle.

Tip: Bootstrap’s Tab plugin can also connect a list group to content panes with data-bs-toggle="list". The small script above does the same job by hand, which helps you see what is happening. Either way, update aria-current when the active item changes.

Further reading

Key takeaways

  • Add table to any table, then modifiers such as table-striped, table-hover and table-sm.
  • Use <caption> and scope on headers for accessible tables.
  • Wrap wide tables in table-responsive so they scroll inside their box on small screens.
  • List groups use list-group and list-group-item, with active, flush, numbered and colour variants.
  • Use list-group-item-action on links or buttons to make list items clickable.

Quick quiz

Check what you learned – pick an answer.

1. Which wrapper stops a wide table from stretching the page on phones?

2. How do you colour a single row red in a Bootstrap table?

3. Which class gives list group items hover and focus styles for clicking?

Practice exercise

Create a train timetable for Chennai Egmore to Madurai with a small, bordered, hoverable table (columns: Train, Departs, Arrives, Days) inside a responsive wrapper, with a dark header and a caption shown above the table. Below it, add a flush list group of three travel tips.

Show a hint

Use table table-sm table-bordered table-hover caption-top and thead class="table-dark".

Show the solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Train timetable</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container py-4">
  <div class="table-responsive">
    <table class="table table-sm table-bordered table-hover caption-top">
      <caption>Sample timetable: Chennai Egmore to Madurai</caption>
      <thead class="table-dark">
        <tr><th scope="col">Train</th><th scope="col">Departs</th><th scope="col">Arrives</th><th scope="col">Days</th></tr>
      </thead>
      <tbody>
        <tr><th scope="row">Morning Express</th><td>06:10</td><td>13:20</td><td>Daily</td></tr>
        <tr><th scope="row">Afternoon Express</th><td>13:45</td><td>21:05</td><td>Mon–Sat</td></tr>
        <tr><th scope="row">Night Mail</th><td>21:30</td><td>05:15</td><td>Daily</td></tr>
      </tbody>
    </table>
  </div>
  <h2 class="h5 mt-4">Travel tips</h2>
  <ul class="list-group list-group-flush">
    <li class="list-group-item">Book early during festival weekends.</li>
    <li class="list-group-item">Carry a printed or digital copy of your ticket.</li>
    <li class="list-group-item">Reach the station at least 30 minutes early.</li>
  </ul>
</div>
</body>
</html>

Progress is saved in this browser only.