Input Groups & Floating Labels

Bootstrap · Lesson 16 of 28 6 min read Updated

Some fields make more sense with something attached: a ₹ sign before a price, "kg" after a weight, "@ethicsmail.in" after a username, or a "Search" button joined to the search box. Bootstrap's input groups join these pieces into one tidy control. Floating labels are a related trick: the label sits inside the field and slides up when the user types, which saves space on mobile forms.

In this lesson you will build both, and learn the HTML order and attributes each one depends on.

Input groups

Wrap the field and its add-ons in a div with input-group. Text add-ons go in a span with input-group-text. Buttons go in directly with their normal btn classes.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Input groups</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-3" style="max-width: 560px">
    <label for="price" class="form-label">Selling price</label>
    <div class="input-group mb-3">
      <span class="input-group-text">₹</span>
      <input type="number" class="form-control" id="price" min="0" aria-describedby="price-unit">
      <span class="input-group-text" id="price-unit">per kg</span>
    </div>

    <label for="username" class="form-label">Choose an email address</label>
    <div class="input-group mb-3">
      <input type="text" class="form-control" id="username" aria-describedby="domain">
      <span class="input-group-text" id="domain">@rohtakmail.in</span>
    </div>

    <label for="coupon" class="form-label">Coupon code</label>
    <div class="input-group mb-3">
      <input type="text" class="form-control" id="coupon" placeholder="DIWALI10">
      <button class="btn btn-outline-success" type="button" id="applyBtn">Apply</button>
    </div>
    <p id="couponMsg" class="small"></p>

    <label for="mobile" class="form-label">Mobile number</label>
    <div class="input-group mb-3">
      <select class="form-select flex-grow-0 w-auto" aria-label="Country code">
        <option>+91</option>
        <option>+971</option>
        <option>+65</option>
      </select>
      <input type="tel" class="form-control" id="mobile" inputmode="numeric">
    </div>

    <p class="mb-1">Height (two inputs)</p>
    <div class="input-group input-group-sm">
      <input type="number" class="form-control" aria-label="Feet" placeholder="5">
      <span class="input-group-text">ft</span>
      <input type="number" class="form-control" aria-label="Inches" placeholder="8">
      <span class="input-group-text">in</span>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    document.getElementById("applyBtn").addEventListener("click", () => {
      const code = document.getElementById("coupon").value.trim().toUpperCase();
      const msg = document.getElementById("couponMsg");
      msg.textContent = code === "DIWALI10" ? "10% off applied." : "That coupon is not valid.";
      msg.className = code === "DIWALI10" ? "small text-success" : "small text-danger";
    });
  </script>
</body>
</html>

How the pieces fit:

  • Add-ons can go before, after, or on both sides of the field. The order in the HTML is the order on screen. Bootstrap rounds only the outer corners of the group.
  • Screen readers do not read input-group-text as part of the label. If the add-on carries meaning (like "per kg" or the email domain), connect it with aria-describedby.
  • Each field still needs a label. When the group has two fields, give each an aria-label, as in the height example.
  • input-group-sm and input-group-lg size the whole group at once.
  • A form-select can sit inside a group too. flex-grow-0 w-auto stops it from taking half the width.

Buttons, dropdowns and checkboxes in groups

You can put any number of buttons in a group, and a dropdown button can open a menu of choices. A checkbox or radio goes inside an input-group-text, with mt-0 to remove its normal top margin:

HTML
<div class="input-group mb-3">
  <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Search in</button>
  <ul class="dropdown-menu">
    <li><a class="dropdown-item" href="#">Books</a></li>
    <li><a class="dropdown-item" href="#">Stationery</a></li>
  </ul>
  <input type="search" class="form-control" aria-label="Search the shop">
  <button class="btn btn-primary" type="submit">Search</button>
</div>

<div class="input-group">
  <div class="input-group-text">
    <input class="form-check-input mt-0" type="checkbox" aria-label="Use as billing address">
  </div>
  <input type="text" class="form-control" aria-label="Address" value="12, MG Road, Kochi">
</div>

By default the parts of a group may wrap onto two lines on a very narrow screen. Add flex-nowrap to the group to keep them on one line.

Floating labels

A floating label starts inside the field, looking like a placeholder. When the field gets focus or has a value, the label shrinks and moves to the top edge, so it stays visible. You get a compact form that never loses its labels.

The structure is strict:

  1. A wrapper with form-floating.
  2. The input first, with form-control and a placeholder attribute.
  3. The label after the input.
HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Floating labels</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" style="max-width: 420px">
    <h1 class="h4 mb-3">Log in to Pune Library</h1>
    <form>
      <div class="form-floating mb-3">
        <input type="email" class="form-control" id="loginEmail" placeholder="name@example.com" autocomplete="email">
        <label for="loginEmail">Email address</label>
      </div>
      <div class="form-floating mb-3">
        <input type="password" class="form-control" id="loginPass" placeholder="Password" autocomplete="current-password">
        <label for="loginPass">Password</label>
      </div>
      <div class="form-floating mb-3">
        <select class="form-select" id="branch">
          <option>Shivajinagar</option>
          <option>Kothrud</option>
          <option>Viman Nagar</option>
        </select>
        <label for="branch">Home branch</label>
      </div>
      <div class="form-floating mb-3">
        <textarea class="form-control" id="note" placeholder="Note" style="height: 100px"></textarea>
        <label for="note">Note for the librarian (optional)</label>
      </div>
      <div class="input-group mb-3">
        <span class="input-group-text">+91</span>
        <div class="form-floating">
          <input type="tel" class="form-control" id="phone" placeholder="Phone">
          <label for="phone">Mobile for OTP</label>
        </div>
      </div>
      <button class="btn btn-primary w-100 py-2" type="submit">Log in</button>
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Why the rules matter:

  • Input before label. Bootstrap's CSS uses the "next sibling" selector (input ~ label) to move the label when the input changes. If the label comes first, CSS cannot reach it.
  • A placeholder is required. The CSS checks :placeholder-shown to know whether the field is empty. The placeholder text itself is never shown, because the label covers it, so any text works.
  • Textareas get a fixed height with style="height: ...", since the rows attribute is not used here.
  • Selects always show their label in the floated position, because a select always has a value.
  • A form-floating block can go inside an input group, as the mobile number field shows.

Floating labels suit short forms like login and sign-up. For long forms with help text under every field, normal labels above the fields are usually clearer.

Common mistakes

  • Label before input in form-floating. The label never moves and overlaps the text. Put the input first.
  • Missing placeholder in a floating field. The label overlaps the value after typing. Add any placeholder text.
  • Keeping Bootstrap 4's input-group-append. It is not needed in Bootstrap 5 and can break the rounded corners.
  • Unlabelled fields inside groups. A ₹ sign is not a label. Add a <label> or aria-label to every input.

Key points

  • input-group joins inputs with input-group-text add-ons, buttons, selects and dropdowns into one control.
  • Link meaningful add-ons with aria-describedby, and label every field.
  • Use input-group-sm, input-group-lg and flex-nowrap to size and control wrapping.
  • A floating label needs form-floating, then the input with a placeholder, then the label.
  • Bootstrap 5 dropped input-group-prepend and input-group-append; add-ons go straight into the group.

Exercise

Create a "Send money" form for a college canteen wallet app. Use floating labels for the receiver's UPI ID and a note. For the amount, use an input group with "₹" before the field and ".00" after it. Below it, add an input group with a text field for a promo code and a "Check" button. Finish with a full-width "Pay" button.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Send money</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" style="max-width: 420px">
    <form>
      <div class="form-floating mb-3">
        <input type="text" class="form-control" id="upi" placeholder="UPI ID">
        <label for="upi">Receiver's UPI ID</label>
      </div>
      <label for="amount" class="form-label">Amount</label>
      <div class="input-group mb-3">
        <span class="input-group-text">₹</span>
        <input type="number" class="form-control" id="amount" min="1">
        <span class="input-group-text">.00</span>
      </div>
      <div class="form-floating mb-3">
        <input type="text" class="form-control" id="note" placeholder="Note">
        <label for="note">Note (for example, lunch with Farhan)</label>
      </div>
      <div class="input-group mb-3">
        <input type="text" class="form-control" aria-label="Promo code" placeholder="Promo code">
        <button class="btn btn-outline-secondary" type="button">Check</button>
      </div>
      <button type="submit" class="btn btn-success w-100">Pay</button>
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Each floating field has the input first, a placeholder, then the label. The amount group puts input-group-text add-ons on both sides of the number field, and the promo code field has an aria-label because it has no visible label.

Quick Quiz

1. Which class wraps the ₹ sign in an input group?

2. In a form-floating block, what must come first?

3. Why does a floating label input need a placeholder even though it is never shown?

4. An input group wraps onto two lines on a small phone. Which class keeps it on one line?

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