Bootstrap Input: 17 Examples of Fields, Groups and States

Bootstrap runs on 13.1% of all websites and holds a 74.8% share of the CSS framework market (W3Techs, October 2026), so many of the forms you edit were built with it.

Below are 17 Bootstrap input examples you can copy, followed by a guide to sizing, states, validation, floating labels, input groups and the v4 to v5 changes.

By Bogdan Sandu ยท Updated October 29, 2026

Every example runs on Bootstrap 5.3.8 from the jsDelivr CDN. Add these tags to your page, then paste an example's HTML, CSS and JS.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css" rel="stylesheet" integrity="sha384-nWN3KqaaVcbEsCjLSV+2jMjh62Iny/+mN3srM8TZeM8N6aj7eDl5wwYggjAJ4/sJ" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>

Text fields: sizes, floating labels and states

The plain .form-control at work: the three sizes, floating labels, readonly, plaintext and disabled fields, a textarea with a character counter and a datalist with suggestions.

Large, default and small inputs in one form

  • form-control-lg
  • form-control-sm
  • col-form-label-lg
  • col-form-label-sm
  • row

A booking card uses each size where it makes sense: a large destination field, default date fields and a small promo code. In a horizontal form the labels take col-form-label-lg and col-form-label-sm so they line up with the field height. The only custom CSS sizes the card.

View code
<div class="card bin-size border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-1">Book a cabin</h2>
    <p class="text-body-secondary small mb-4">Fjordline Cabins, free cancellation up to 7 days before arrival.</p>
    <form class="bin-size-form">
      <div class="row mb-3">
        <label for="bin-size-where" class="col-sm-3 col-form-label col-form-label-lg">Where</label>
        <div class="col-sm-9">
          <input type="text" class="form-control form-control-lg" id="bin-size-where" placeholder="Lofoten, Norway" autocomplete="off">
        </div>
      </div>
      <div class="row mb-3">
        <label for="bin-size-in" class="col-sm-3 col-form-label">Dates</label>
        <div class="col-sm-9">
          <div class="row g-2">
            <div class="col-12 col-sm-6">
              <input type="date" class="form-control" id="bin-size-in" aria-label="Check-in date" value="2026-12-18">
            </div>
            <div class="col-12 col-sm-6">
              <input type="date" class="form-control" id="bin-size-out" aria-label="Check-out date" value="2026-12-22">
            </div>
          </div>
        </div>
      </div>
      <div class="row mb-4">
        <label for="bin-size-promo" class="col-sm-3 col-form-label col-form-label-sm">Promo code</label>
        <div class="col-sm-9">
          <input type="text" class="form-control form-control-sm text-uppercase" id="bin-size-promo" placeholder="WINTER26" aria-describedby="bin-size-promo-help">
          <div id="bin-size-promo-help" class="form-text">Optional. Codes are not case sensitive.</div>
        </div>
      </div>
      <div class="d-grid d-sm-flex justify-content-sm-end">
        <button type="submit" class="btn btn-dark btn-lg px-4">Check availability</button>
      </div>
    </form>
  </div>
</div>

Floating labels on a delivery form

  • form-floating
  • form-select
  • form-control-plaintext
  • textarea height
  • row g-2

Every field sits in .form-floating with the input first, the label second and a placeholder that never shows. The select keeps its label floated, the order number is a readonly form-control-plaintext field, and the textarea gets an explicit height because rows does not work with floating labels.

View code
<div class="card bin-float border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Delivery details</h2>
    <form class="row g-2 bin-float-form">
      <div class="col-12">
        <div class="form-floating">
          <input type="text" readonly class="form-control-plaintext" id="bin-float-order" value="NB-20417">
          <label for="bin-float-order">Order number</label>
        </div>
      </div>
      <div class="col-sm-6">
        <div class="form-floating">
          <input type="text" class="form-control" id="bin-float-name" placeholder="Full name" autocomplete="name" value="Ines Carvalho">
          <label for="bin-float-name">Full name</label>
        </div>
      </div>
      <div class="col-sm-6">
        <div class="form-floating">
          <input type="tel" class="form-control" id="bin-float-phone" placeholder="Phone" autocomplete="tel">
          <label for="bin-float-phone">Phone for the courier</label>
        </div>
      </div>
      <div class="col-sm-8">
        <div class="form-floating">
          <input type="text" class="form-control" id="bin-float-street" placeholder="Street" autocomplete="street-address">
          <label for="bin-float-street">Street and number</label>
        </div>
      </div>
      <div class="col-sm-4">
        <div class="form-floating">
          <select class="form-select" id="bin-float-slot">
            <option>Morning</option>
            <option selected>Afternoon</option>
            <option>Evening</option>
          </select>
          <label for="bin-float-slot">Time slot</label>
        </div>
      </div>
      <div class="col-12">
        <div class="form-floating">
          <textarea class="form-control bin-float-notes" id="bin-float-notes" placeholder="Notes"></textarea>
          <label for="bin-float-notes">Notes for the courier</label>
        </div>
      </div>
      <div class="col-12 d-grid mt-3">
        <button type="submit" class="btn btn-primary btn-lg">Save delivery details</button>
      </div>
    </form>
  </div>
</div>

Readonly, plaintext and disabled fields

  • readonly
  • form-control-plaintext
  • disabled
  • input-group
  • form-text

Press Select next to the invoice ID.

An invoice panel shows the three locked states side by side. The plaintext account row drops the field chrome, the readonly invoice ID stays focusable and selectable (the Select button highlights it for copying), and the disabled fields are greyed out, skipped by Tab and left out of the submitted data.

View code
<div class="card bin-st border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Invoice settings</h2>
    <div class="row mb-3">
      <label for="bin-st-account" class="col-sm-4 col-form-label">Account</label>
      <div class="col-sm-8">
        <input type="text" readonly class="form-control-plaintext" id="bin-st-account" value="studio@harborline.example">
      </div>
    </div>
    <div class="mb-3">
      <label for="bin-st-invoice" class="form-label">Invoice ID</label>
      <div class="input-group">
        <input type="text" readonly class="form-control font-monospace" id="bin-st-invoice" value="INV-2026-0418" aria-describedby="bin-st-invoice-help">
        <button class="btn btn-outline-secondary bin-st-select" type="button" aria-controls="bin-st-invoice">Select</button>
      </div>
      <div id="bin-st-invoice-help" class="form-text bin-st-status" aria-live="polite">Readonly: you can select and copy it, and it is submitted.</div>
    </div>
    <div class="row g-3">
      <div class="col-sm-6">
        <label for="bin-st-currency" class="form-label">Billing currency</label>
        <select class="form-select" id="bin-st-currency" disabled aria-describedby="bin-st-currency-help">
          <option>EUR, euro</option>
        </select>
        <div id="bin-st-currency-help" class="form-text">Contact support to change it.</div>
      </div>
      <div class="col-sm-6">
        <label for="bin-st-vat" class="form-label">VAT number</label>
        <input type="text" class="form-control" id="bin-st-vat" value="PT 509 123 456" disabled>
      </div>
    </div>
  </div>
</div>

Textarea with a character counter

  • textarea.form-control
  • maxlength
  • progress
  • form-text
  • aria-describedby

Type in the bio field.

The textarea has maxlength="160", so the browser enforces the limit and the script only reports it. The counter is a form-text linked through aria-describedby, and a 4px Bootstrap progress bar fills up and switches to the warning and danger colors near the limit.

View code
<div class="card bin-cnt border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Public profile</h2>
    <label for="bin-cnt-bio" class="form-label">Short bio</label>
    <textarea class="form-control" id="bin-cnt-bio" rows="4" maxlength="160" aria-describedby="bin-cnt-count">Brand designer in Porto. I build identity systems for small food and drink makers.</textarea>
    <div class="progress bin-cnt-bar mt-2" role="progressbar" aria-label="Characters used" aria-valuemin="0" aria-valuemax="160" aria-valuenow="0">
      <div class="progress-bar bg-success"></div>
    </div>
    <div class="d-flex justify-content-between mt-1">
      <div class="form-text mt-0">Shown on your portfolio and in search results.</div>
      <div id="bin-cnt-count" class="form-text mt-0 fw-semibold text-nowrap ms-3">0 / 160</div>
    </div>
  </div>
</div>

Datalist with time zone suggestions

  • datalist
  • list attribute
  • form-control
  • Intl.DateTimeFormat
  • form-text

Start typing a city, for example Tokyo.

A plain .form-control with a list attribute gets native suggestions from a datalist, and the user can still type any value. The browser draws the suggestion popup, so its look differs between browsers. A short script shows the current time in the chosen zone and flags names it does not recognize.

View code
<div class="card bin-dl border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Meeting preferences</h2>
    <label for="bin-dl-zone" class="form-label">Your time zone</label>
    <input class="form-control" list="bin-dl-zones" id="bin-dl-zone" value="Europe/Lisbon" autocomplete="off" aria-describedby="bin-dl-time">
    <datalist id="bin-dl-zones">
      <option value="America/New_York">
      <option value="America/Chicago">
      <option value="America/Los_Angeles">
      <option value="America/Sao_Paulo">
      <option value="Europe/Lisbon">
      <option value="Europe/Berlin">
      <option value="Africa/Lagos">
      <option value="Asia/Kolkata">
      <option value="Asia/Singapore">
      <option value="Asia/Tokyo">
      <option value="Australia/Sydney">
    </datalist>
    <div id="bin-dl-time" class="form-text bin-dl-time" aria-live="polite">Local time there: loading</div>
  </div>
</div>

Input groups with prefixes, suffixes and buttons

Input groups attach text, buttons and dropdowns to a field so they render as one control. These four cover a price with a currency menu, a URL with a fixed prefix and suffix, a search box with a clear button and a quantity stepper.

Price field with a currency dropdown

  • input-group
  • dropdown-toggle
  • input-group-text
  • inputmode="decimal"
  • Intl.NumberFormat

Open the currency menu and type an amount.

A dropdown button, a symbol add-on and the amount field sit in one .input-group. Picking a currency updates the button text and the symbol, and the preview line formats the amount with Intl.NumberFormat, so yen drops the decimals on its own. The label stays outside the group, as the docs require.

View code
<div class="card bin-cur border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Set your project rate</h2>
    <label for="bin-cur-amount" class="form-label">Fixed price</label>
    <div class="input-group input-group-lg">
      <button class="btn btn-outline-secondary dropdown-toggle bin-cur-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"><span class="visually-hidden">Currency: </span><span class="bin-cur-code">USD</span></button>
      <ul class="dropdown-menu">
        <li><button class="dropdown-item active" type="button" data-bin-cur="USD" data-bin-sym="$">USD, US dollar</button></li>
        <li><button class="dropdown-item" type="button" data-bin-cur="EUR" data-bin-sym="โ‚ฌ">EUR, euro</button></li>
        <li><button class="dropdown-item" type="button" data-bin-cur="GBP" data-bin-sym="ยฃ">GBP, pound sterling</button></li>
        <li><button class="dropdown-item" type="button" data-bin-cur="JPY" data-bin-sym="ยฅ">JPY, Japanese yen</button></li>
      </ul>
      <span class="input-group-text bin-cur-sym" aria-hidden="true">$</span>
      <input type="text" class="form-control" id="bin-cur-amount" inputmode="decimal" value="2400" aria-describedby="bin-cur-preview">
    </div>
    <div id="bin-cur-preview" class="form-text bin-cur-preview" aria-live="polite">Clients see: $2,400.00</div>
  </div>
</div>

Site address with a fixed prefix and suffix

  • input-group-text
  • has-validation
  • pattern
  • invalid-feedback
  • was-validated

Type a studio name and press Claim address.

The protocol and the domain are input-group-text add-ons on both sides of the field, so people type only the part they choose. The script lowercases the value and turns spaces into hyphens as they type, and the pattern attribute plus .has-validation on the group shows the feedback without breaking the rounded corners.

View code
<div class="card bin-url border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-1">Claim your portfolio address</h2>
    <p class="text-body-secondary small mb-3">You can change it once every 30 days.</p>
    <form class="bin-url-form" novalidate>
      <label for="bin-url-name" class="form-label">Address</label>
      <div class="input-group has-validation">
        <span class="input-group-text bin-url-fix">https&#58;&#47;&#47;</span>
        <input type="text" class="form-control" id="bin-url-name" placeholder="your-studio" pattern="[a-z0-9\-]{3,30}" required autocomplete="off" spellcheck="false" aria-describedby="bin-url-help bin-url-error">
        <span class="input-group-text bin-url-fix">.folio.page</span>
        <div id="bin-url-error" class="invalid-feedback">Use 3 to 30 lowercase letters, numbers or hyphens.</div>
      </div>
      <div id="bin-url-help" class="form-text bin-url-status" aria-live="polite">Lowercase letters, numbers and hyphens.</div>
      <div class="d-grid mt-3">
        <button type="submit" class="btn btn-primary">Claim address</button>
      </div>
    </form>
  </div>
</div>

Quantity stepper in a cart

  • input-group-sm
  • type="number"
  • min / max
  • btn-outline-secondary
  • aria-label

Use the plus and minus buttons.

Each stepper is a small input group: a minus button, a number field with min and max, and a plus button. The buttons call stepDown() and stepUp(), disable themselves at the limits and update the line and order totals. The custom CSS hides the native spinner arrows and fixes the group width.

View code
<div class="card bin-qty border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Your bag</h2>
    <ul class="list-unstyled mb-0">
      <li class="d-flex flex-wrap align-items-center gap-3 py-3 border-bottom bin-qty-row" data-bin-price="38">
        <div class="bin-qty-thumb bg-warning-subtle" aria-hidden="true"></div>
        <div class="flex-grow-1">
          <p class="fw-semibold mb-0">Linen tote bag</p>
          <p class="small text-body-secondary mb-0">Sand, $38.00 each</p>
        </div>
        <div class="input-group input-group-sm bin-qty-group">
          <button class="btn btn-outline-secondary bin-qty-minus" type="button" aria-label="Decrease tote bag quantity"><i class="bi bi-dash-lg" aria-hidden="true"></i></button>
          <input type="number" class="form-control text-center bin-qty-input" id="bin-qty-tote" value="1" min="1" max="10" aria-label="Tote bag quantity">
          <button class="btn btn-outline-secondary bin-qty-plus" type="button" aria-label="Increase tote bag quantity"><i class="bi bi-plus-lg" aria-hidden="true"></i></button>
        </div>
        <p class="fw-semibold mb-0 text-end bin-qty-line">$38.00</p>
      </li>
      <li class="d-flex flex-wrap align-items-center gap-3 py-3 border-bottom bin-qty-row" data-bin-price="12.5">
        <div class="bin-qty-thumb bg-success-subtle" aria-hidden="true"></div>
        <div class="flex-grow-1">
          <p class="fw-semibold mb-0">Beeswax wraps</p>
          <p class="small text-body-secondary mb-0">Set of 3, $12.50 each</p>
        </div>
        <div class="input-group input-group-sm bin-qty-group">
          <button class="btn btn-outline-secondary bin-qty-minus" type="button" aria-label="Decrease beeswax wraps quantity"><i class="bi bi-dash-lg" aria-hidden="true"></i></button>
          <input type="number" class="form-control text-center bin-qty-input" id="bin-qty-wraps" value="2" min="1" max="10" aria-label="Beeswax wraps quantity">
          <button class="btn btn-outline-secondary bin-qty-plus" type="button" aria-label="Increase beeswax wraps quantity"><i class="bi bi-plus-lg" aria-hidden="true"></i></button>
        </div>
        <p class="fw-semibold mb-0 text-end bin-qty-line">$25.00</p>
      </li>
    </ul>
    <div class="d-flex justify-content-between align-items-center pt-3">
      <span class="text-body-secondary">Subtotal</span>
      <span class="fs-5 fw-bold bin-qty-total" aria-live="polite">$63.00</span>
    </div>
  </div>
</div>

Inputs with a little JavaScript

Bootstrap styles the field and a few lines of vanilla JavaScript add the behavior: a password toggle with a strength meter, a one-time code that moves between boxes, range sliders with live values, a synced color picker and a file input that lists what you picked.

Password with show/hide toggle and strength meter

  • input-group
  • aria-pressed
  • progress
  • bg-danger / bg-success
  • Bootstrap Icons

Type a password, then press the eye button.

The eye button is an input group add-on that switches the field between password and text and reports its state with aria-pressed. A Bootstrap progress bar and a checklist score the password on four simple rules, and the strength label sits in a live region. The meter is guidance only; the real rules belong on the server.

View code
<div class="card bin-pw border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Choose a new password</h2>
    <label for="bin-pw-field" class="form-label">New password</label>
    <div class="input-group">
      <input type="password" class="form-control" id="bin-pw-field" autocomplete="new-password" aria-describedby="bin-pw-label bin-pw-rules">
      <button class="btn btn-outline-secondary bin-pw-toggle" type="button" aria-label="Show password" aria-pressed="false" aria-controls="bin-pw-field"><i class="bi bi-eye" aria-hidden="true"></i></button>
    </div>
    <div class="progress bin-pw-bar mt-2" role="progressbar" aria-label="Password strength" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
      <div class="progress-bar"></div>
    </div>
    <p id="bin-pw-label" class="form-text mb-2 bin-pw-label" aria-live="polite">Strength: not set</p>
    <ul id="bin-pw-rules" class="list-unstyled small mb-0 bin-pw-rules">
      <li data-bin-rule="length"><i class="bi bi-circle" aria-hidden="true"></i> At least 12 characters</li>
      <li data-bin-rule="case"><i class="bi bi-circle" aria-hidden="true"></i> Upper and lowercase letters</li>
      <li data-bin-rule="digit"><i class="bi bi-circle" aria-hidden="true"></i> A number</li>
      <li data-bin-rule="symbol"><i class="bi bi-circle" aria-hidden="true"></i> A symbol such as ! or #</li>
    </ul>
  </div>
</div>

One-time code boxes with auto-advance

  • form-control-lg
  • inputmode="numeric"
  • autocomplete="one-time-code"
  • data-bs-theme
  • paste handling

Click the first box and type six digits, or paste a code.

Six form-control-lg boxes in a CSS grid hold one digit each. Typing a digit moves focus to the next box, Backspace on an empty box goes back, arrow keys move between boxes, and pasting or autofilling a full code spreads it across all six. The Verify button stays disabled until the code is complete.

View code
<div class="card bin-otp border-0 shadow">
  <div class="card-body p-4 text-center">
    <div class="bin-otp-icon mx-auto mb-3" aria-hidden="true"><i class="bi bi-phone"></i></div>
    <h2 class="h5 mb-1" id="bin-otp-title">Enter the 6-digit code</h2>
    <p class="text-body-secondary small mb-4">We sent it by text message to the number ending in 47.</p>
    <form class="bin-otp-form">
      <div class="bin-otp-boxes mb-3" role="group" aria-labelledby="bin-otp-title">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="one-time-code" aria-label="Digit 1 of 6">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="off" aria-label="Digit 2 of 6">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="off" aria-label="Digit 3 of 6">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="off" aria-label="Digit 4 of 6">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="off" aria-label="Digit 5 of 6">
        <input type="text" class="form-control form-control-lg text-center bin-otp-digit" inputmode="numeric" autocomplete="off" aria-label="Digit 6 of 6">
      </div>
      <p class="small mb-3 bin-otp-status" role="status"></p>
      <div class="d-grid">
        <button type="submit" class="btn btn-info btn-lg bin-otp-verify" disabled>Verify</button>
      </div>
    </form>
    <p class="small text-body-secondary mt-3 mb-0">Did not get it? <a href="#" class="link-info">Send a new code</a></p>
  </div>
</div>

Range sliders with live values

  • form-range
  • output
  • min / max / step
  • aria-valuetext
  • scoped thumb color

Drag the sliders.

Two .form-range sliders drive a small loan estimate, and each label row holds an output element that shows the current value. The script also sets aria-valuetext so screen readers hear 12,000 dollars instead of 12000. The thumb color is compiled into Bootstrap 5.3 CSS, so the teal comes from short scoped rules on the slider pseudo-elements.

View code
<div class="card bin-rng border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Equipment loan estimate</h2>
    <div class="mb-3">
      <div class="d-flex justify-content-between align-items-baseline">
        <label for="bin-rng-amount" class="form-label mb-1">Amount</label>
        <output for="bin-rng-amount" class="fw-semibold bin-rng-amount-out">$12,000</output>
      </div>
      <input type="range" class="form-range" id="bin-rng-amount" min="2000" max="50000" step="500" value="12000">
    </div>
    <div class="mb-4">
      <div class="d-flex justify-content-between align-items-baseline">
        <label for="bin-rng-term" class="form-label mb-1">Term</label>
        <output for="bin-rng-term" class="fw-semibold bin-rng-term-out">36 months</output>
      </div>
      <input type="range" class="form-range" id="bin-rng-term" min="12" max="72" step="6" value="36">
    </div>
    <div class="rounded-3 p-3 bin-rng-result d-flex justify-content-between align-items-center flex-wrap gap-2">
      <div>
        <p class="small mb-0">Monthly payment</p>
        <p class="small mb-0 text-body-secondary">at 6.9% APR, example only</p>
      </div>
      <output for="bin-rng-amount bin-rng-term" class="fs-3 fw-bold bin-rng-pay" aria-live="polite">$370</output>
    </div>
  </div>
</div>

Color picker synced with a hex field

  • form-control-color
  • input-group-text
  • pattern
  • --bs-btn-bg
  • contrast check

Pick a swatch or type a hex value.

A form-control-color picker and a hex text field in an input group stay in sync both ways, and preset swatches set both at once. The chosen color goes into the --bs-btn-bg, --bs-btn-border-color and hover variables of a preview button, and the script picks white or dark text and prints the contrast ratio so you can see whether the brand color passes 4.5:1.

View code
<div class="card bin-col border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Brand color</h2>
    <div class="d-flex flex-wrap gap-2 align-items-end mb-3">
      <div>
        <label for="bin-col-picker" class="form-label">Picker</label>
        <input type="color" class="form-control form-control-color" id="bin-col-picker" value="#0f766e" title="Choose a brand color">
      </div>
      <div class="flex-grow-1 bin-col-hexwrap">
        <label for="bin-col-hex" class="form-label">Hex value</label>
        <div class="input-group">
          <span class="input-group-text">#</span>
          <input type="text" class="form-control font-monospace text-uppercase" id="bin-col-hex" value="0F766E" maxlength="6" pattern="[0-9A-Fa-f]{6}" spellcheck="false" autocomplete="off">
        </div>
      </div>
    </div>
    <div class="d-flex flex-wrap gap-2 mb-4" role="group" aria-label="Preset colors">
      <button type="button" class="bin-col-swatch" data-bin-hex="#0f766e" style="--bin-sw: #0f766e" aria-label="Teal, 0F766E"></button>
      <button type="button" class="bin-col-swatch" data-bin-hex="#b45309" style="--bin-sw: #b45309" aria-label="Amber, B45309"></button>
      <button type="button" class="bin-col-swatch" data-bin-hex="#be123c" style="--bin-sw: #be123c" aria-label="Rose, BE123C"></button>
      <button type="button" class="bin-col-swatch" data-bin-hex="#4338ca" style="--bin-sw: #4338ca" aria-label="Indigo, 4338CA"></button>
      <button type="button" class="bin-col-swatch" data-bin-hex="#facc15" style="--bin-sw: #facc15" aria-label="Sun yellow, FACC15"></button>
    </div>
    <div class="d-flex flex-wrap align-items-center gap-3 border rounded-3 p-3">
      <button type="button" class="btn bin-col-btn">Start free trial</button>
      <p class="small mb-0 bin-col-ratio" aria-live="polite">White text, 5.5:1</p>
    </div>
  </div>
</div>

File input that lists the chosen files

  • type="file"
  • multiple
  • accept
  • list-group
  • Bootstrap Icons

Choose one or more files.

The file field is a normal .form-control with multiple and accept, so Bootstrap styles the button part and the browser handles the picker. On change the script lists each file with an icon, its name and a readable size, flags anything over 10 MB in red and offers a Clear button that resets the input.

View code
<div class="card bin-file border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-3">Attach project files</h2>
    <label for="bin-file-input" class="form-label">Briefs, moodboards or archives</label>
    <input class="form-control" type="file" id="bin-file-input" multiple accept=".pdf,.png,.jpg,.jpeg,.zip" aria-describedby="bin-file-help">
    <div id="bin-file-help" class="form-text">PDF, PNG, JPG or ZIP, up to 10 MB each.</div>
    <ul class="list-group mt-3 bin-file-list" aria-live="polite">
      <li class="list-group-item text-body-secondary small bin-file-empty"><i class="bi bi-paperclip me-1" aria-hidden="true"></i>No files chosen yet.</li>
    </ul>
    <div class="d-flex justify-content-between align-items-center mt-3">
      <span class="small text-body-secondary bin-file-sum">0 files</span>
      <button type="button" class="btn btn-outline-secondary btn-sm bin-file-clear" disabled>Clear</button>
    </div>
  </div>
</div>

Validation states, theming and dark mode

Field-level .is-valid and .is-invalid feedback, a set of inputs re-themed through the --bs-* CSS variables and a panel that switches data-bs-theme between light and dark.

Field-level validation with feedback text

  • is-valid
  • is-invalid
  • valid-feedback
  • has-validation
  • aria-invalid

Press Create team with empty fields, then fix them.

Instead of .was-validated on the whole form, each field gets .is-valid or .is-invalid when the user leaves it, and again as they fix it. The checks come from the browser (required, pattern, type="email", min and max), the feedback blocks are tied to their fields with aria-describedby, and aria-invalid carries the state to screen readers.

View code
<div class="card bin-val border-0 shadow-sm">
  <div class="card-body p-4">
    <h2 class="h5 mb-1">Create a team</h2>
    <p class="text-body-secondary small mb-4">Every field is required.</p>
    <form class="row g-3 bin-val-form" novalidate>
      <div class="col-12">
        <label for="bin-val-handle" class="form-label">Team handle</label>
        <div class="input-group has-validation">
          <span class="input-group-text">@</span>
          <input type="text" class="form-control" id="bin-val-handle" pattern="[a-z0-9_]{3,15}" required autocomplete="off" aria-describedby="bin-val-handle-ok bin-val-handle-err">
          <div id="bin-val-handle-ok" class="valid-feedback">Nice, that handle is free.</div>
          <div id="bin-val-handle-err" class="invalid-feedback">3 to 15 lowercase letters, numbers or underscores.</div>
        </div>
      </div>
      <div class="col-sm-7">
        <label for="bin-val-email" class="form-label">Billing email</label>
        <input type="email" class="form-control" id="bin-val-email" required autocomplete="email" aria-describedby="bin-val-email-err">
        <div id="bin-val-email-err" class="invalid-feedback">Enter an address like name@company.com.</div>
      </div>
      <div class="col-sm-5">
        <label for="bin-val-seats" class="form-label">Seats</label>
        <input type="number" class="form-control" id="bin-val-seats" min="1" max="50" required aria-describedby="bin-val-seats-err">
        <div id="bin-val-seats-err" class="invalid-feedback">Between 1 and 50.</div>
      </div>
      <div class="col-12">
        <label for="bin-val-role" class="form-label">Your role</label>
        <select class="form-select" id="bin-val-role" required aria-describedby="bin-val-role-err">
          <option value="" selected>Choose one</option>
          <option>Designer</option>
          <option>Developer</option>
          <option>Project manager</option>
        </select>
        <div id="bin-val-role-err" class="invalid-feedback">Pick the role closest to yours.</div>
      </div>
      <div class="col-12 d-grid">
        <button type="submit" class="btn btn-success btn-lg bin-val-submit">Create team</button>
      </div>
      <div class="col-12 bin-val-done" role="status"></div>
    </form>
  </div>
</div>

Inputs themed with --bs-* CSS variables

  • --bs-body-bg
  • --bs-border-color
  • --bs-border-radius
  • --bs-tertiary-bg
  • --bs-btn-*

Click into a field to see the themed focus ring.

The wrapper redefines --bs-body-bg, --bs-body-color, --bs-border-color, --bs-secondary-color, --bs-tertiary-bg and the radius variables, and every .form-control, .form-select and .input-group-text inside follows them without new classes. The focus ring is compiled to blue in Bootstrap 5.3.8, so one scoped rule sets the terracotta ring.

View code
<div class="bin-theme">
  <h2 class="bin-theme-title mb-1">The Reading Room</h2>
  <p class="bin-theme-lead mb-4">One long essay every Sunday morning, chosen by our editors.</p>
  <form class="row g-3 bin-theme-form">
    <div class="col-sm-6">
      <label for="bin-theme-name" class="form-label">First name</label>
      <input type="text" class="form-control" id="bin-theme-name" placeholder="Margot" autocomplete="given-name">
    </div>
    <div class="col-sm-6">
      <label for="bin-theme-topic" class="form-label">Favorite topic</label>
      <select class="form-select" id="bin-theme-topic">
        <option>Architecture</option>
        <option selected>Food and farming</option>
        <option>Science</option>
        <option>Travel</option>
      </select>
    </div>
    <div class="col-12">
      <label for="bin-theme-email" class="form-label">Email</label>
      <div class="input-group">
        <span class="input-group-text"><i class="bi bi-envelope-paper" aria-hidden="true"></i></span>
        <input type="email" class="form-control" id="bin-theme-email" placeholder="you@example.com" autocomplete="email">
        <button class="btn bin-theme-btn" type="submit">Subscribe</button>
      </div>
      <div class="form-text">Free. Unsubscribe from any issue.</div>
    </div>
  </form>
</div>

Dark mode settings panel with a theme switch

  • data-bs-theme
  • form-switch
  • is-invalid
  • disabled
  • font-monospace

Toggle Dark panel to switch the card theme.

The card carries data-bs-theme="dark", so only this block switches to the dark palette, including the input group add-on, the select arrow and the lighter red of the invalid field. The switch flips the attribute between dark and light on the card, which shows that no input needs its own dark class.

View code
<div class="card bin-dark shadow" data-bs-theme="dark">
  <div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2 py-3">
    <h2 class="h6 mb-0">Deploy settings</h2>
    <div class="form-check form-switch mb-0">
      <input class="form-check-input bin-dark-switch" type="checkbox" role="switch" id="bin-dark-switch" checked>
      <label class="form-check-label small" for="bin-dark-switch">Dark panel</label>
    </div>
  </div>
  <div class="card-body p-4">
    <div class="row g-3">
      <div class="col-sm-7">
        <label for="bin-dark-name" class="form-label">Project name</label>
        <input type="text" class="form-control" id="bin-dark-name" value="atlas-storefront">
      </div>
      <div class="col-sm-5">
        <label for="bin-dark-region" class="form-label">Region</label>
        <select class="form-select" id="bin-dark-region">
          <option selected>Frankfurt</option>
          <option>Virginia</option>
          <option>Singapore</option>
        </select>
      </div>
      <div class="col-12">
        <label for="bin-dark-build" class="form-label">Build command</label>
        <div class="input-group">
          <span class="input-group-text"><i class="bi bi-terminal" aria-hidden="true"></i></span>
          <input type="text" class="form-control font-monospace" id="bin-dark-build" value="npm run build" spellcheck="false">
        </div>
      </div>
      <div class="col-sm-7">
        <label for="bin-dark-hook" class="form-label">Webhook URL</label>
        <input type="text" class="form-control is-invalid" id="bin-dark-hook" value="hooks.example/deploy" aria-invalid="true" aria-describedby="bin-dark-hook-err">
        <div id="bin-dark-hook-err" class="invalid-feedback">The URL must start with https.</div>
      </div>
      <div class="col-sm-5">
        <label for="bin-dark-plan" class="form-label">Plan</label>
        <input type="text" class="form-control" id="bin-dark-plan" value="Team, 8 seats" disabled>
      </div>
    </div>
  </div>
</div>

What is a Bootstrap input?

Add .form-control to a native input or textarea and Bootstrap restyles it. Every field gets the same borders, padding, focus ring and state styling, which keeps rendering consistent across browsers and devices (Bootstrap documentation). Nothing replaces the underlying element.

The field is still a plain HTML element, so the type attribute decides which built-in controls and checks the browser applies. The Bootstrap docs advise email for addresses and number for numeric values.

A single field usually starts with a <label class="form-label"> whose for value matches the input id. The input follows with .form-control, the same id and a suitable type. Both sit in a div with .mb-3 for spacing, because Bootstrap 5 uses margin utilities instead of a form-group class. If the field needs a hint, a .form-text block goes underneath and connects to the input through aria-describedby.

That pattern repeats for every field. How the rows line up is a layout question, and the full Bootstrap form guide covers it.

Examples here target Bootstrap 5.3. The official docs list v5.3.8 as the current release as of October 2026, and Bootstrap 4 markup differs in several places.

Which input types does Bootstrap style?

Text-like types take .form-control. Color, range and select get their own classes.

Input typeBootstrap classNote
text, email, password, number, date, url, textarea.form-controlAppearance only, parsing stays with the browser
file.form-controlAdd multiple for several files, sm and lg sizes work
color.form-control and .form-control-colorNeeds type=”color”, width set by $form-color-width
range.form-rangeReplaces the browser slider look
select.form-selectSeparate class, not .form-control

Number, date and email fields still validate the way the browser decides. Bootstrap changes how they look, not what they accept.

Datalist is a bit awkward. The text field takes .form-control, but the browser draws the suggestion list, and the docs call that styling inconsistent across browsers and operating systems. File inputs are simpler, since one class covers single and multiple uploads.

Checkboxes and radios sit outside this group. They use .form-check-input inside a .form-check wrapper, which the Bootstrap checkbox styles cover separately.

How to change Bootstrap input size and width

Add .form-control-sm to shrink a field or .form-control-lg to enlarge it. Width comes from the parent column, because .form-control fills 100% of its container by default.

Sizing with classes

Both size classes change padding and font size, so the field height follows. The padding values below come from the shared $input-btn-* Sass variables in v5.3.8, which buttons use too.

ClassVertical paddingHorizontal padding
.form-control (default)0.375rem0.75rem
.form-control-sm0.25rem0.5rem
.form-control-lg0.5rem1rem

Horizontal forms need one more class. Labels take .col-form-label-sm or .col-form-label-lg so they track the input size, according to the Bootstrap layout docs.

Input groups size from the wrapper instead, with .input-group-sm or .input-group-lg.

Setting width with the grid

Fields stack by default, since Bootstrap applies display: block and width: 100% to almost every form control. For side-by-side fields, wrap them in .row g-3 and give each one a .col-md-* column. .col-auto sizes a column to its content, and if you only want a fixed share, a width utility such as .w-50 on the input itself does the job.

Column classes come from Bootstrap’s grid system, and the breakpoint suffix (md, lg) decides where columns stack.

A field that is narrower than its parent usually lost .form-control or inherited a custom width rule. Check those two first.

Disabled, readonly and plaintext inputs

Disabled blocks all interaction, readonly keeps the field focusable, and .form-control-plaintext strips the field styling from a readonly input.

A disabled field looks lighter than a normal one, gets skipped by keyboard focus and is left out of the submitted data. A readonly field stays in the tab order, its text can be selected and copied, and its value is submitted.

Readonly fits values people need to copy, like an order ID. Disabled text cannot be selected, according to the Bootstrap docs.

For plaintext, swap .form-control for .form-control-plaintext on the readonly input. The border and background disappear, margin and padding stay, and in 5.3 the text color follows the body color variable.

Putting disabled on a <fieldset> disables every native control inside it. Link-styled buttons are the exception. An <a class="btn"> only receives pointer-events: none, so keyboard users can still reach it.

Add tabindex="-1" and the aria-disabled attribute to those links, so the ARIA state reaches assistive technologies.

The focus ring is the one state left to the framework. Its width, blur and color come from the $input-btn-focus-* variables, which point to the $focus-ring-* variables in v5.3.8.

How does Bootstrap input validation work?

Bootstrap styles the browser’s :valid and :invalid pseudo-classes once a parent .was-validated class is present. Without that class, every empty required field would show as invalid the moment the page loads.

The styles reach input and textarea elements with .form-control, selects with .form-select, and .form-check groups.

Client-side validation

Add novalidate to the form to hide the browser’s default error bubbles. The constraint validation API stays available, so a short JavaScript handler can still call checkValidity().

  1. Add novalidate and the needs-validation class to the form.
  2. Mark each field required or give it a pattern the browser can check.
  3. Place an .invalid-feedback block after each field, plus .valid-feedback for success messages.
  4. Attach a submit listener that calls checkValidity() and, on failure, runs preventDefault() and stopPropagation().
  5. Add .was-validated to the form inside the same listener.

After an Ajax submission, remove .was-validated to reset the form’s appearance. Custom messages come from setCustomValidity.

There is an accessibility catch here. The Bootstrap 5.3 documentation states that client-side custom validation styles and tooltips are not exposed to assistive technologies.

For anyone building accessible forms, the docs point to the server-side route or the browser’s default validation messages instead. The .invalid-tooltip variant falls under the same warning.

Server-side states

.is-valid and .is-invalid work without a .was-validated parent. An .invalid-feedback block next to the field appears once the state class is on the input. Link each message to its field with aria-describedby, which accepts several ids when the field already points to help text.

The docs recommend client-side validation in general, yet the accessibility note steers cautious teams toward server-rendered states. For production forms, browser defaults or server classes are the safer pair, and that is where I would land too.

Validation inside input groups

Input groups need an extra .has-validation class on the wrapper to fix border radius issues, according to the Bootstrap docs.

Place the feedback block inside the group, as a sibling of the input. Validation styles apply to one .form-control per input group, so a group with two text fields needs separate handling.

Floating labels or input groups: which wrapper fits?

Floating labels suit short single-line fields where space is tight, and an input group fits when the field needs something attached, such as a prefix or a button. For long or dense forms, a plain input with a visible label is still the better call.

Floating labels

Wrapping an input and its label in .form-floating produces floating labels. Two markup rules decide whether the effect works. Every input needs a non-empty placeholder, because the CSS-only version detects an empty field with :placeholder-shown. And the input comes first with the label second, so a sibling selector can reach the label.

The placeholder text never shows. Only the label is visible, according to the Bootstrap 5.3 docs.

Coverage is narrower than a plain input. Floating labels work on .form-control fields (inputs and textareas) and on .form-select, where the label always stays in its floated position.

Selects with size or multiple are not supported, and a custom textarea height needs an explicit height instead of the rows attribute.

On the plus side, the label stays visible after the user types, and short forms save vertical space. It also works with validation styles, disabled inputs and readonly plaintext.

The costs are real though. The field is taller, since $form-floating-height is 3.5rem plus the border. A placeholder is required even though nobody sees it. The floated label scales to 85% and drops to 0.65 opacity by default. And inside an input group, validation feedback has to be shown with JavaScript, where it sits outside .form-floating but inside the group.

Version 5.3.0 gave floating labels a background color to fix textarea support, per the Bootstrap migration notes.

Input groups

An input group attaches text, buttons or button groups to the edge of an input, and the result renders as one control.

Add-ons sit inside .input-group as .input-group-text elements or buttons. Icons from the Bootstrap Icons set fit the text add-on well for search and email fields.

A Bootstrap button works as the action add-on, for example a search or apply button next to the field.

Labels and help text go outside the group. Groups wrap by default (flex-wrap: wrap), and .flex-nowrap stops it. Select and file add-ons need .form-select and .form-control, because the browser default versions are not supported.

Border radius only reaches the first and last children. A hidden element such as one with .d-none in either position breaks the corners, and the docs say a fix will most likely wait for v6.

That is a different problem from the validation radius issue that .has-validation solves.

The upside is that prefix, suffix and action stay visually attached to the field, and one group takes checkboxes, radios, dropdowns and custom selects. On the downside, the label has to sit outside the group, hidden children break the corners, and validation styles only work with a single input per group.

Choosing between them

If the field needs something attached, use an input group. If the form is short and a label is the only extra, use floating labels.

A login or signup form with a handful of short fields suits floating labels. A search box, a currency amount or a username with an @ prefix wants an input group. Long label text, textareas with instructions and dense admin screens do better with a plain label above the field.

The two also combine. The docs place .form-floating inside .input-group, so a floating field can carry an add-on.

Which accessibility attributes does a Bootstrap input need?

A Bootstrap input needs an accessible name, a programmatic link to its help and error text, and an error message written in text. Bootstrap supplies the classes, and the markup that satisfies WCAG stays with the author.

The Bootstrap 5.3 docs say a visible label is generally the best approach, and they list four other ways to name an input. The ranking below is mine, since the docs do not order them.

A visible label comes first, meaning a <label> whose for value matches the input id. Next is the same label with .visually-hidden, for designs that drop the visible text. After that comes aria-labelledby, which points the input at an element that already holds the text, and then aria-label, which puts the name directly on the input, as Bootstrap’s own input group examples do. The title attribute works as a name but is the weakest explicit option.

Without any of these, assistive technologies can fall back to the placeholder, which disappears once the field has content. Hidden text also helps only screen reader users, so the docs still favor a visible label.

WCAG 2.2 splits the work across criteria. SC 3.3.2 (Level A) asks for labels or instructions on inputs, while SC 1.3.1 covers whether the label is programmatically tied to the control (W3C Understanding documents).

SC 3.3.1 (Level A) requires a detected input error to be identified and described in text. The color and icon from .is-invalid can signal the error, and the W3C text allows that, but .invalid-feedback carries the description.

Feedback text takes the danger color through $form-feedback-invalid-color. The color contrast of that red against your page background is yours to test after any theme change.

The classes cover the field. A web accessibility checklist covers the page around it.

How to customize Bootstrap input styles with Sass and CSS variables

Override $input-* Sass variables before Bootstrap’s variables load when you compile it yourself. If you ship prebuilt CSS, override --bs-* CSS variables or use data-bs-theme instead.

Overriding Sass variables

Every Bootstrap Sass variable carries the !default flag, so a value you assign first wins. Overrides must come after the functions import and before the variables import. The full order is functions, your overrides, then variables, variables-dark, maps, mixins and root.

The add-on background, $input-group-addon-bg, defaults to var(--bs-tertiary-bg) in v5.3.8, so it already follows the color mode.

Validation colors live in the $form-validation-states map, which holds color, icon, tooltip colors, focus shadow and border color for each state. Browsers only support valid and invalid, but the docs note that custom styles can add more.

Sass overrides only work when you compile. A CDN build is already compiled, so changing a variable there does nothing.

Recent Dart Sass versions print deprecation warnings when compiling Bootstrap’s source. The docs say these do not block compilation and can be ignored while a long-term fix is in progress.

Theming at runtime

Color modes arrived in Bootstrap 5.3.0, and .form-control was rebuilt on CSS variables to follow them.

data-bs-theme="dark" on the html element themes every input, and on a single container it themes only that block. Validation colors switch automatically through --bs-form-valid-color and --bs-form-invalid-color, using green-300 and red-300 based values in dark mode. The select arrow has its own dark SVG variable, $form-select-indicator-dark. Any data-bs-theme name works, such as blue, once you override the CSS variables for it.

Setting $enable-dark-mode to false removes the built-in dark mode. Switching $color-mode-type to media-query makes it automatic but gives up per-component switching, according to the Bootstrap docs.

The Bootstrap team states that CSS variable coverage stays incomplete until v6 (v5.2.0 migration notes), so some input properties still need Sass. For one-off changes on a CDN build, a plain stylesheet works best, as this guide to overriding Bootstrap CSS shows.

If you set custom borders and backgrounds, they need a 3:1 non-text contrast ratio against their surroundings, per the WCAG figure cited in the Bootstrap Sass docs.

What changed for inputs from Bootstrap 4 to Bootstrap 5?

Bootstrap 5 removed the form-specific layout classes, merged native and custom controls into one class set, and dropped the wrapper divs around input group add-ons.

Bootstrap 4Bootstrap 5Effect on the input
.form-groupMargin utilities such as .mb-3Spacing moves to utilities
.form-row, .form-inline.row with .g-* gutters, .col-autoLayout moves to the grid
.input-group-prepend, .input-group-appendDirect children of .input-groupWrapper divs deleted
.custom-select, .custom-range.form-select, .form-rangeOne class per control
.custom-file, .form-control-file.form-controlFile input styled like a text input

The online guides disagree on the last row. Some third-party migration material, including the MDB guide, maps .custom-file to .form-file. That class only existed in the early v5 alphas: v5.0.0-alpha1 added it and v5.0.0-alpha3 dropped it for .form-control.

The official Bootstrap 5 migration notes say custom styles on top of .form-control replaced both file classes, and the 5.3 form docs use only .form-control for file inputs. Follow the official notes.

Version 5.0.0 also made form labels require .form-label. .form-text no longer sets display, so inline or block help text depends on the element you choose. Controls use min-height instead of a fixed height where possible, and floating labels became a fully supported component.

Changes inside Bootstrap 5 matter too. Version 5.2.0 refined the border radius on form controls, a visible shift when upgrading from earlier 5.x releases. Version 5.3.0 moved .form-control onto CSS variables for color modes and fixed date and time input display in WebKit browsers.

Bootstrap 5 also dropped Internet Explorer 10 and 11, legacy Edge below 16, Firefox below 60 and Safari below 12 (migration notes). Teams that still serve those browsers face a cross-browser compatibility decision before they upgrade.

Bootstrap 4.6.2, published July 19, 2022, is the final release on the 4.x line, and Bootstrap 4 reached end of life on January 1, 2023 (Bootstrap blog and end-of-life notice). Bootstrap 5 is the only stable major version still maintained. Bootstrap 6 reached its first alpha on October 8, 2026 and is not production-ready (Bootstrap blog).

For existing forms, start with wrappers and labels. Input groups can wait a step, and the file and select classes go last.

Bootstrap Input FAQ

Does a Bootstrap input need jQuery?

No. Bootstrap 5 dropped jQuery, and the .form-control styling is plain CSS.

JavaScript appears only in the validation starter script and in dropdown buttons inside input groups, which need Bootstrap’s JavaScript plus Popper (the bundle file includes both).

Should I load Bootstrap from a CDN or install it with npm?

Use a CDN for prototypes and npm for customized builds. The official docs show jsDelivr links pinned to a version such as bootstrap@5.3.8.

An npm install places Bootstrap’s Sass source in node_modules, which you need before any $input-* override can take effect.

How do I use a Bootstrap input in React?

Use Form.Control from React-Bootstrap, which renders the same .form-control class. React-Bootstrap 2.0.0 and later targets Bootstrap 5.

Wrap fields in Form.Group to pass a controlId, since Bootstrap 5 removed the .form-group class itself.

Can Bootstrap validation replace server-side checks?

No. Bootstrap only styles the result, and client-side checks run in the browser, where users can change or bypass them.

Keep server-side validation in place, and use .is-invalid and .is-valid to display what the server returns.

When to Move Past Bootstrap Input Styling

A Bootstrap input remains the right choice until a form needs behavior that .form-control, .input-group and .form-floating cannot express. Beyond that point, custom CSS on the native element is the cleaner path.

Escalate slowly. Standard fields stay on .form-control, validation stays server-rendered or browser-default for as long as it can, and custom CSS is reserved for components that hit a documented limit.

That sequence keeps color mode support and consistent browser rendering for every field that needs no exception. The trade-off of the last step is losing both for the fields restyled by hand.

Two documented limits point the same way. The docs expect the input group radius fix in v6 and tie full CSS variable coverage to v6, so hand-written CSS for those components is the plan until a stable v6 ships (Bootstrap 6 reached its first alpha on October 8, 2026; position verified October 29, 2026, against v5.3.8).

A working Bootstrap contact form is a practical place to test these choices.