Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions js/tests/visual/floating-label.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,26 @@
<option value="3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mattis mi at lobortis rutrum. Phasellus varius a risus non lobortis. Ut id congue enim. Quisque facilisis elit ac elit dapibus aliquet nec sit amet arcu. Morbi vitae ultricies eros. Proin varius augue in tristique pretium. Morbi at ullamcorper elit, at ullamcorper massa. Vivamus suscipit quam quis viverra eleifend.</option>
</select>
</div>
<div class="form-floating">
<label for="floatingDate">Date</label>
<input type="date" class="form-control" id="floatingDate">
</div>
<div class="form-floating">
<label for="floatingTime">Time</label>
<input type="time" class="form-control" id="floatingTime">
</div>
<div class="form-floating">
<label for="floatingDatetime">Datetime local</label>
<input type="datetime-local" class="form-control" id="floatingDatetime">
</div>
<div class="form-floating form-floating-always">
<label for="alwaysFloatingInput">Email address</label>
<input type="email" class="form-control" id="alwaysFloatingInput" placeholder="name@example.com">
</div>
<div class="form-floating form-floating-always">
<label for="alwaysFloatingPassword">Password</label>
<input type="password" class="form-control" id="alwaysFloatingPassword" placeholder="••••••••">
</div>
<div class="form-floating mb-3">
<label for="floatingInputDisabled">Email address</label>
<input type="email" class="form-control" id="floatingInputDisabled" placeholder="name@example.com" disabled>
Expand Down
69 changes: 68 additions & 1 deletion scss/forms/_floating-labels.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ $form-floating-tokens: defaults(
color: transparent;
}

// Show the placeholder once focused so the floated label has a hint.
&:focus::placeholder {
color: var(--control-placeholder-color);
}

&:focus,
&:not(:placeholder-shown) {
padding-top: var(--form-floating-input-padding-t);
Expand All @@ -91,12 +96,39 @@ $form-floating-tokens: defaults(
}
}

// Date and time inputs (and `<select>`) do not support `:placeholder-shown`
// the way textual inputs do, so keep their padding in the floated state.
// See https://github.com/twbs/bootstrap/issues/39257.
> :is(
.form-control:is(
[type="date"],
[type="month"],
[type="week"],
[type="time"],
[type="datetime-local"]
),
select.form-control
) {
padding-top: var(--form-floating-input-padding-t);
padding-bottom: var(--form-floating-input-padding-b);
}

// The label precedes the control in the DOM so screen readers announce it
// before the field's value, so we look forward with `:has()` to react to the
// control's state (focus, value, disabled, etc.).
> label:has(~ .form-control:focus),
> label:has(~ .form-control:not(:placeholder-shown)),
> label:has(~ .form-control-plaintext) {
> label:has(~ .form-control-plaintext),
> label:has(~ :is(
.form-control:is(
[type="date"],
[type="month"],
[type="week"],
[type="time"],
[type="datetime-local"]
),
select.form-control
)) {
transform: var(--form-floating-label-transform);
}

Expand Down Expand Up @@ -130,4 +162,39 @@ $form-floating-tokens: defaults(
color: var(--form-floating-label-disabled-color);
}
}

// Opt in to an always-floated label with a visible placeholder. Works on the
// `.form-floating` itself or as a wrapper around one.
.form-floating-always {
&.form-floating,
.form-floating {
> .form-control {
padding-top: var(--form-floating-input-padding-t);
padding-bottom: var(--form-floating-input-padding-b);

&::placeholder,
&:focus::placeholder {
color: var(--control-placeholder-color);
}
}

> label {
transform: var(--form-floating-label-transform);
}

> label:has(~ textarea)::after {
position: absolute;
inset: var(--form-floating-padding-y) calc(var(--form-floating-padding-x) * .5);
z-index: -1;
height: var(--form-floating-label-height);
content: "";
background-color: var(--form-floating-label-bg);
@include border-radius(var(--btn-input-border-radius));
}

> label:has(~ textarea:disabled)::after {
background-color: var(--form-floating-label-disabled-bg);
}
}
}
}
5 changes: 5 additions & 0 deletions scss/forms/_form-control.scss
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ $form-control-sizes: defaults(
padding: 0;
}

// Soften `/` and `:` separators between date/time fields (WebKit/Blink only).
&::-webkit-datetime-edit-text {
color: var(--control-placeholder-color);
}

// File inputs
&[type="file"] {
overflow: hidden; // prevent pseudo element button overlap
Expand Down
30 changes: 29 additions & 1 deletion site/src/content/docs/forms/floating-labels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ css_layer: forms

Wrap a pair of `<input class="form-control">` and `<label>` elements in `.form-floating` to enable floating labels with Bootstrap’s textual form fields.

A non-empty `placeholder` attribute is required on each `<input>` as our CSS-only floating label implementation relies on the `:placeholder-shown` pseudo-element to detect when the input is empty. The placeholder text itself is not visible; only the `<label>` is shown to users.
A non-empty `placeholder` attribute is required on each `<input>` as our CSS-only floating label implementation relies on the `:placeholder-shown` pseudo-element to detect when the input is empty. The placeholder stays transparent until the control is focused, then uses the normal placeholder color. Only the `<label>` is shown when the field is empty and unfocused.

Also note that the `<label>` must come first so that screen readers announce it before the field’s value; our CSS reacts to the control’s state with the `:has()` selector.

Expand Down Expand Up @@ -66,6 +66,19 @@ Select elements work in the same way, but unlike `<input>`s, they’ll always sh
</select>
</div>`} />

## Date and time

Date, time, datetime-local, month, and week inputs also always show the `<label>` in its floated state. These controls do not support placeholders the way textual inputs do, so a `placeholder` attribute is not required (and is ignored by most browsers on these types).

<Example class="vstack gap-3" code={`<div class="form-floating">
<label for="floatingDate">Date</label>
<input type="date" class="form-control" id="floatingDate">
</div>
<div class="form-floating">
<label for="floatingTime">Time</label>
<input type="time" class="form-control" id="floatingTime">
</div>`} />

## Disabled

Add the `disabled` boolean attribute on an input, a textarea or a select to give it a grayed out appearance, remove pointer events, and prevent focusing.
Expand Down Expand Up @@ -132,6 +145,21 @@ When using `.input-group` and `.form-floating` along with form validation, place
</div>
</div>`} />

## Always floating

Add `.form-floating-always` to keep the label floated and show the placeholder. Use it on the `.form-floating` itself or as a wrapper around one.

<Example class="vstack gap-3" code={`<div class="form-floating form-floating-always">
<label for="alwaysFloatingInput">Email address</label>
<input type="email" class="form-control" id="alwaysFloatingInput" placeholder="name@example.com">
</div>
<div class="form-floating form-floating-always">
<label for="alwaysFloatingPassword">Password</label>
<input type="password" class="form-control" id="alwaysFloatingPassword" placeholder="••••••••">
</div>`} />

By default, placeholders stay transparent until the control is focused. With `.form-floating-always`, the placeholder stays visible in both states.

## Layout

When working with the Bootstrap grid system, be sure to place form elements within column classes.
Expand Down
17 changes: 2 additions & 15 deletions site/src/content/docs/forms/form-control.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ If you want to have `<input readonly>` elements in your form styled as plain tex

Nearly all input types are supported, but not all are documented here. Input types requiring additional classes or changes are shown below.

### Date and time
### Date & Time

No additional classes are needed for date and time inputs. Exact functionality and appearance are dependent on the browser and operating system.
No additional classes are needed for date and time inputs. Exact functionality and appearance are dependent on the browser and operating system. However, you can also use [our datepicker component]([[docsref:/forms/datepicker]]) to create a more consistent experience across browsers with JavaScript.

<Example class="vstack gap-3" code={`<input type="datetime-local" class="form-control" aria-label="Date and time picker" value="2026-02-24T13:00">
<input type="datetime-local" class="form-control" aria-label="Date and time picker with seconds" value="2026-02-24T13:00:30" step="1">
Expand Down Expand Up @@ -288,19 +288,6 @@ You can also combine with the `.form-control-caret` class to add a caret to inpu
<option value="Chicago">
</datalist>`} />

### Date & time

No additional classes are needed for date and time inputs.

<Example code={`<label for="exampleDateTimeInput" class="form-label">Date and time picker</label>
<input type="datetime-local" class="form-control" id="exampleDateTimeInput" value="2026-02-24T13:00">`} />

<Example code={`<label for="exampleDateInput" class="form-label">Date picker</label>
<input type="date" class="form-control" id="exampleDateInput" value="2026-02-24">`} />

<Example code={`<label for="exampleTimeInput" class="form-label">Time picker</label>
<input type="time" class="form-control" id="exampleTimeInput" value="13:00">`} />

### File

`input[type="file"]` elements are styled with the `::file-selector-button` pseudo element to add a button to the input. You only need to add the `.form-control` class, plus any size modifiers you need.
Expand Down
Loading