Floating Labels

Floating labels are one of the few genuinely new form patterns Bootstrap introduced with version 5 — there is no equivalent in Bootstrap 4. A label sits inside the field like placeholder text until the field gains focus or already has a value, at which point the label animates up and shrinks to sit above the field instead. This lesson builds it for TrailMap's email and trail name fields.

What's New: The .form-floating Pattern

Wrap an input (or textarea, or select) and its label together in a .form-floating container. No JavaScript is involved — the animation is pure CSS, driven by the input's :focus and :placeholder-shown states.

<!-- TrailMap: submitter email, floating label -->
<div class="form-floating mb-3">
  <input type="email" class="form-control" id="submitterEmail" placeholder="name@example.com">
  <label for="submitterEmail">Your Email</label>
</div>

The Required Markup Order: Input First, Then Label

Unlike almost every other Bootstrap form pattern, order here is not just a style preference — it's required. The <label> must come after the <input> in the HTML, because the floating animation is driven by a CSS sibling selector that reads the input's state and then styles the label that follows it. Put the label first and the float effect simply won't work.

<!-- Wrong order — label floats will not trigger -->
<div class="form-floating">
  <label for="trailName">Trail Name</label>
  <input type="text" class="form-control" id="trailName" placeholder="Trail Name">
</div>

<!-- Correct order -->
<div class="form-floating">
  <input type="text" class="form-control" id="trailName" placeholder="Trail Name">
  <label for="trailName">Trail Name</label>
</div>
New in Bootstrap 5: Also note the placeholder attribute is still required on the input even though its text is invisible in normal use — it's what lets the :placeholder-shown selector detect an empty field and keep the label floated down until there's real content.

Floating Labels with Select and Textarea

The same wrapper works with .form-select and <textarea>, not just text inputs — the input-then-label order rule still applies to both.

<!-- Floating label on a select -->
<div class="form-floating mb-3">
  <select class="form-select" id="trailDifficulty">
    <option value="easy">Easy</option>
    <option value="moderate">Moderate</option>
  </select>
  <label for="trailDifficulty">Difficulty</label>
</div>

<!-- Floating label on a textarea (fixed height required) -->
<div class="form-floating">
  <textarea class="form-control" id="trailDescription" placeholder="Description" style="height: 100px"></textarea>
  <label for="trailDescription">Description</label>
</div>

Worked Example: TrailMap Email and Trail Name Floating Fields

Both fields together, stacked with the usual .mb-3 spacing between them:

<form>
  <div class="form-floating mb-3">
    <input type="text" class="form-control" id="trailName" placeholder="Trail Name">
    <label for="trailName">Trail Name</label>
  </div>
  <div class="form-floating mb-3">
    <input type="email" class="form-control" id="submitterEmail" placeholder="name@example.com">
    <label for="submitterEmail">Your Email</label>
  </div>
  <button type="submit" class="btn btn-primary">Submit Trail</button>
</form>

Click into "Trail Name" and its label shrinks and slides up; click away while it's empty and it drops back down to fill the field again — exactly the same behavior you'd expect from a native mobile app text field.

Next up: Not every control is text-based. See how Bootstrap styles sliders and toggle switches in Range & Switches next.