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>
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.