Dropdowns

Bootstrap · Lesson 19 of 28 6 min read Updated

A dropdown is a button that opens a small menu of choices: "Sort by", an account menu with Profile and Logout, a list of export formats. Bootstrap's dropdown plugin opens and positions the menu, closes it when you click elsewhere, and lets keyboard users move through the items with the arrow keys.

In this lesson you will build dropdowns, change their direction and alignment, put forms inside them, and control them with options and JavaScript.

A basic dropdown

A dropdown needs a wrapper, a toggle button and a menu:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Dropdown</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-4">
    <div class="dropdown d-inline-block">
      <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
        Sort by
      </button>
      <ul class="dropdown-menu">
        <li><h6 class="dropdown-header">Price</h6></li>
        <li><button class="dropdown-item" type="button" data-sort="low">Low to high</button></li>
        <li><button class="dropdown-item" type="button" data-sort="high">High to low</button></li>
        <li><hr class="dropdown-divider"></li>
        <li><button class="dropdown-item active" type="button" aria-current="true" data-sort="new">Newest first</button></li>
        <li><button class="dropdown-item disabled" type="button" aria-disabled="true">Best rated (coming soon)</button></li>
      </ul>
    </div>
    <p class="mt-3" id="sortLabel">Showing: Newest first</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    document.querySelectorAll("[data-sort]").forEach((item) => {
      item.addEventListener("click", () => {
        document.querySelectorAll("[data-sort]").forEach((i) => {
          i.classList.remove("active");
          i.removeAttribute("aria-current");
        });
        item.classList.add("active");
        item.setAttribute("aria-current", "true");
        document.getElementById("sortLabel").textContent = "Showing: " + item.textContent;
      });
    });
  </script>
</body>
</html>

The pieces:

  • dropdown on the wrapper gives it position: relative, so the menu is placed next to the button.
  • The toggle needs data-bs-toggle="dropdown" and aria-expanded="false". Bootstrap flips aria-expanded to true while the menu is open. dropdown-toggle adds the small caret arrow.
  • dropdown-menu is the list; each choice gets dropdown-item. Use <a> items for links to other pages and <button> items for actions on this page.
  • dropdown-header labels a section, dropdown-divider draws a line, active (with aria-current) marks the current choice, and disabled greys out an item.

Try the keyboard: Tab to the button, press Enter or the down arrow to open, use the arrow keys to move, Enter to choose and Escape to close.

Split buttons

A split button has a main action and a separate arrow for more choices, like "Save" with "Save as PDF" and "Save as Excel" in the menu. Put both buttons in a btn-group, and give the arrow dropdown-toggle-split plus hidden text, because it has no visible label:

HTML
<div class="btn-group">
  <button type="button" class="btn btn-success">Download invoice</button>
  <button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split"
          data-bs-toggle="dropdown" aria-expanded="false">
    <span class="visually-hidden">More download formats</span>
  </button>
  <ul class="dropdown-menu">
    <li><a class="dropdown-item" href="/invoice.pdf">PDF</a></li>
    <li><a class="dropdown-item" href="/invoice.xlsx">Excel</a></li>
  </ul>
</div>

Direction and alignment

By default a menu opens downwards, lined up with the start (left) edge of the button. Change the direction with a class on the wrapper, and the alignment with a class on the menu:

ClassWhereEffect
dropupWrapperOpens upwards
dropend, dropstartWrapperOpens to the right or left
dropdown-center, dropup-centerWrapperMenu centred under or above the button
dropdown-menu-endMenuLined up with the end (right) edge of the button
dropdown-menu-lg-endMenuEnd-aligned from lg up (needs data-bs-display="static" on the toggle)

Use dropdown-menu-end for menus near the right edge of the screen, such as an account menu in a navbar, so the menu does not run off the screen. Bootstrap (through Popper) also flips a menu automatically if there is not enough room below it.

Dark menus

In Bootstrap 5.3, add data-bs-theme="dark" to the dropdown (or a parent) to get a dark menu that matches a dark navbar:

HTML
<div class="dropdown" data-bs-theme="dark">
  <button class="btn btn-dark dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Account</button>
  <ul class="dropdown-menu dropdown-menu-end">
    <li><a class="dropdown-item" href="/profile">Profile</a></li>
    <li><a class="dropdown-item" href="/logout">Log out</a></li>
  </ul>
</div>

Forms and auto-close

By default a menu closes on any click, including clicks inside it. That is a problem when the menu holds a form or checkboxes. The data-bs-auto-close option on the toggle controls this:

ValueMenu closes when you click...
true (default)Inside or outside the menu
insideOnly inside the menu
outsideOnly outside the menu (good for forms and filters)
falseOnly on the toggle button or Escape
HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Filter dropdown</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-4">
    <div class="dropdown">
      <button class="btn btn-primary dropdown-toggle" type="button" id="filterBtn" data-bs-toggle="dropdown"
              data-bs-auto-close="outside" aria-expanded="false">
        Filter hotels
      </button>
      <form class="dropdown-menu p-3" style="min-width: 260px" id="filterForm">
        <p class="fw-semibold mb-2">City</p>
        <div class="form-check">
          <input class="form-check-input" type="checkbox" id="jaipur" value="Jaipur" checked>
          <label class="form-check-label" for="jaipur">Jaipur</label>
        </div>
        <div class="form-check mb-3">
          <input class="form-check-input" type="checkbox" id="kochi" value="Kochi">
          <label class="form-check-label" for="kochi">Kochi</label>
        </div>
        <label for="maxPrice" class="form-label">Max price per night</label>
        <select class="form-select mb-3" id="maxPrice">
          <option value="1500">₹1,500</option>
          <option value="3000" selected>₹3,000</option>
          <option value="6000">₹6,000</option>
        </select>
        <button type="submit" class="btn btn-success w-100">Apply</button>
      </form>
    </div>
    <p class="mt-3" id="summary">No filter applied yet.</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    const toggle = document.getElementById("filterBtn");
    document.getElementById("filterForm").addEventListener("submit", (event) => {
      event.preventDefault();
      const cities = [...document.querySelectorAll("#filterForm input:checked")].map((c) => c.value);
      const price = document.getElementById("maxPrice").value;
      document.getElementById("summary").textContent = `Cities: ${cities.join(", ") || "any"}, up to ₹${price}`;
      bootstrap.Dropdown.getOrCreateInstance(toggle).hide(); // close the menu from code
    });
    toggle.addEventListener("shown.bs.dropdown", () => console.log("Filter menu opened"));
  </script>
</body>
</html>

The menu here is a <form> with padding (p-3) instead of a list. With data-bs-auto-close="outside", ticking boxes and choosing a price keeps the menu open; clicking elsewhere on the page closes it. After "Apply", the script closes it with the JavaScript API.

The JavaScript API

JavaScript
const toggleBtn = document.getElementById("filterBtn");
const dd = bootstrap.Dropdown.getOrCreateInstance(toggleBtn);
dd.show();
dd.hide();
dd.toggle();

Call the API on the toggle button, not on the menu. Dropdowns fire show.bs.dropdown, shown.bs.dropdown, hide.bs.dropdown and hidden.bs.dropdown on the toggle. The hide events include a clickEvent property, so you can find out which click closed the menu.

Common mistakes

  • Using bootstrap.min.js without Popper. Dropdowns throw an error because they need Popper for positioning. Use the bundle file.
  • A form inside a menu that keeps closing. Add data-bs-auto-close="outside" to the toggle.
  • A menu cut off by its container. A parent with overflow: hidden (such as a table-responsive wrapper) can clip it. Move the dropdown, or set data-bs-boundary or a Popper strategy.
  • A split toggle with no hidden text. Screen readers hear only "button". Add a visually-hidden label.

Key points

  • A dropdown is a wrapper (dropdown), a toggle with data-bs-toggle="dropdown" and a dropdown-menu of dropdown-items.
  • dropdown-header, dropdown-divider, active and disabled organise the menu.
  • dropup, dropend, dropstart and the -center classes change direction; dropdown-menu-end changes alignment.
  • data-bs-auto-close="outside" keeps a menu open while the user works inside it.
  • bootstrap.Dropdown.getOrCreateInstance(toggle) gives show(), hide() and toggle(), and *.bs.dropdown events report changes.

Exercise

Create a language picker for a news site from Lucknow. It should be a dropup button aligned to the end, labelled "Language: English", with a header "Choose language" and three items: English, Hindi and Urdu. When an item is clicked, update the button text and mark the chosen item as active.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Language picker</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-5 d-flex justify-content-end align-items-end" style="min-height: 300px">
    <div class="dropup">
      <button class="btn btn-outline-dark dropdown-toggle" type="button" id="langBtn" data-bs-toggle="dropdown" aria-expanded="false">Language: English</button>
      <ul class="dropdown-menu dropdown-menu-end">
        <li><h6 class="dropdown-header">Choose language</h6></li>
        <li><button class="dropdown-item active" type="button" aria-current="true">English</button></li>
        <li><button class="dropdown-item" type="button">Hindi</button></li>
        <li><button class="dropdown-item" type="button">Urdu</button></li>
      </ul>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    const items = document.querySelectorAll(".dropdown-item");
    items.forEach((item) => {
      item.addEventListener("click", () => {
        items.forEach((i) => { i.classList.remove("active"); i.removeAttribute("aria-current"); });
        item.classList.add("active");
        item.setAttribute("aria-current", "true");
        document.getElementById("langBtn").textContent = "Language: " + item.textContent;
      });
    });
  </script>
</body>
</html>

dropup on the wrapper opens the menu upwards and dropdown-menu-end lines it up with the right edge. Clicking an item closes the menu by default, and the script moves the active class and updates the label.

Quick Quiz

1. Which attribute turns a button into a dropdown toggle?

2. A menu contains a small form, but it closes as soon as the user clicks a checkbox. What fixes it?

3. Which class makes a menu open upwards?

4. On which element do you call bootstrap.Dropdown.getOrCreateInstance()?

Learn Bootstrap with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call