Forms
Forms are where users talk to your website: sign-up, login, admission enquiry, checkout. Browsers draw form fields differently on every device, and plain fields look dated. Bootstrap gives every field the same clean look, a clear focus ring and consistent sizes, while keeping the real HTML form elements underneath.
In this lesson you will style every common form control and arrange them into grid, horizontal and inline layouts. The next two lessons add input groups, floating labels and validation.
Text fields, labels and help text
Each field gets three things: a <label> with form-label, the field with form-control, and optional help text with form-text. Wrap each group in a div with a bottom margin such as mb-3.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admission enquiry</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">
<h1 class="h4 mb-3">Admission enquiry</h1>
<form>
<div class="mb-3">
<label for="name" class="form-label">Student name</label>
<input type="text" class="form-control" id="name" autocomplete="name">
</div>
<div class="mb-3">
<label for="email" class="form-label">Parent's email</label>
<input type="email" class="form-control" id="email" aria-describedby="emailHelp" autocomplete="email">
<div id="emailHelp" class="form-text">We send the fee structure to this address.</div>
</div>
<div class="mb-3">
<label for="course" class="form-label">Class</label>
<select class="form-select" id="course">
<option value="">Choose...</option>
<option>Class 9</option>
<option>Class 10</option>
<option>Class 11 (Science)</option>
<option>Class 11 (Commerce)</option>
</select>
</div>
<div class="mb-3">
<label for="message" class="form-label">Questions</label>
<textarea class="form-control" id="message" rows="3"></textarea>
</div>
<fieldset class="mb-3">
<legend class="col-form-label pt-0">Preferred batch</legend>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="batch" id="morning" value="morning" checked>
<label class="form-check-label" for="morning">Morning</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="batch" id="evening" value="evening">
<label class="form-check-label" for="evening">Evening</label>
</div>
</fieldset>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="hostel">
<label class="form-check-label" for="hostel">Hostel needed</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="updates" checked>
<label class="form-check-label" for="updates">Send WhatsApp updates</label>
</div>
<button type="submit" class="btn btn-primary">Send enquiry</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>Important details:
- Every
<label>has aforthat matches the field'sid. Clicking the label focuses the field, and screen readers read the label when the field is focused. A placeholder is not a replacement for a label: it disappears as soon as the user types. aria-describedby="emailHelp"links the help text to the field, so screen readers read it too.<select>usesform-select, notform-control. It gets a custom arrow that matches the design.- Checkboxes and radios use a wrapper
form-check, the inputform-check-input, and the labelform-check-label. Addform-check-inlineto put them side by side. - A related group of radios belongs in a
<fieldset>with a<legend>, so the question ("Preferred batch") is read with each option. form-switchturns a checkbox into an on/off switch. Addrole="switch"so screen readers announce it as a switch.
Sizes, states and other controls
<!-- Sizes -->
<input class="form-control form-control-lg" type="text" placeholder="Large">
<input class="form-control form-control-sm" type="text" placeholder="Small">
<select class="form-select form-select-sm">...</select>
<!-- States -->
<input class="form-control" type="text" value="KV Rohtak" readonly>
<input class="form-control" type="text" value="Locked" disabled>
<input class="form-control-plaintext" type="text" value="roll-no-1024" readonly>
<!-- Other controls -->
<input class="form-control" type="file" id="photo" accept="image/*">
<input class="form-range" type="range" min="0" max="10" step="1" id="rating">
<input class="form-control form-control-color" type="color" value="#7952b3">readonlyshows a value that cannot be changed but is still submitted.disabledgreys the field out, and its value is not submitted.form-control-plaintextdisplays a read-only value as plain text, without the box.- File inputs use
form-controltoo, andform-rangestyles a slider. - To disable a whole section at once, put the fields in
<fieldset disabled>.
Form layouts with the grid
Forms use the same grid as everything else. Put row g-3 on the form (or a div) and size each field with column classes. This checkout form has two fields per row on tablets and one per row on phones:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Delivery address</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">
<h1 class="h5 mb-3">Delivery address</h1>
<form class="row g-3">
<div class="col-md-6">
<label for="first" class="form-label">First name</label>
<input type="text" class="form-control" id="first" autocomplete="given-name">
</div>
<div class="col-md-6">
<label for="last" class="form-label">Last name</label>
<input type="text" class="form-control" id="last" autocomplete="family-name">
</div>
<div class="col-12">
<label for="address" class="form-label">House and street</label>
<input type="text" class="form-control" id="address" autocomplete="street-address">
</div>
<div class="col-md-5">
<label for="city" class="form-label">City</label>
<input type="text" class="form-control" id="city" value="Pune">
</div>
<div class="col-md-4">
<label for="state" class="form-label">State</label>
<select id="state" class="form-select">
<option>Maharashtra</option>
<option>Kerala</option>
<option>Rajasthan</option>
</select>
</div>
<div class="col-md-3">
<label for="pin" class="form-label">PIN code</label>
<input type="text" class="form-control" id="pin" inputmode="numeric" maxlength="6">
</div>
<div class="col-12">
<button type="submit" class="btn btn-success">Deliver here</button>
</div>
</form>
<h2 class="h6 mt-5">Horizontal layout</h2>
<form>
<div class="row mb-3">
<label for="phone" class="col-sm-3 col-form-label">Mobile</label>
<div class="col-sm-9"><input type="tel" class="form-control" id="phone" autocomplete="tel"></div>
</div>
</form>
<h2 class="h6 mt-4">Inline layout</h2>
<form class="row row-cols-md-auto g-2 align-items-center">
<div class="col-12">
<label class="visually-hidden" for="track">Order number</label>
<input type="text" class="form-control" id="track" placeholder="Order number">
</div>
<div class="col-12"><button type="submit" class="btn btn-outline-primary">Track</button></div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>Three layouts in one example:
- Grid form:
row g-3withcol-md-*columns. The PIN field usesinputmode="numeric"so phones show a number keypad. - Horizontal form: each field is its own
row, the label is a column withcol-form-label(which lines the text up with the input vertically), and the input sits in the remaining columns. - Inline form:
row-cols-md-automakes each column only as wide as its content frommdupwards, so the field and button sit on one line. The label is kept for screen readers withvisually-hidden.
Common mistakes
- Placeholder instead of a label. The hint vanishes when typing and many screen readers ignore it. Always add a
<label>, hidden withvisually-hiddenif the design has no room. form-controlon a<select>. The arrow looks wrong. Useform-select.- Duplicate
idvalues. If two fields share an id, the second label points at the first field. - Disabled fields you expect to submit. Disabled values are never sent; use
readonlyfor values that should be sent but not edited.
Key points
- Use
form-labelon labels,form-controlon inputs and textareas,form-selecton selects, andform-textfor help text. - Checkboxes, radios and switches use
form-check,form-check-inputandform-check-label; addform-switchandrole="switch"for switches. - Link every label with
forandid, link help text witharia-describedby, and group radios in a<fieldset>with a<legend>. - Lay out forms with the grid (
row g-3andcol-*), horizontal rows withcol-form-label, or inline withrow-cols-*-auto. - Bootstrap 4's
form-group,form-row,custom-selectandcustom-controlno longer exist in Bootstrap 5.
Exercise
Build a registration form for a cricket tournament in Kochi. Include: team name, captain's name and phone number side by side from md upwards, a select for the age group (Under 14, Under 17, Open), a radio group for match timing (Morning or Evening) inside a fieldset, a switch for "We need a practice pitch", a range slider for number of players (11 to 15), and a submit button.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tournament registration</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">
<form class="row g-3">
<div class="col-12">
<label for="team" class="form-label">Team name</label>
<input type="text" class="form-control" id="team">
</div>
<div class="col-md-6">
<label for="captain" class="form-label">Captain's name</label>
<input type="text" class="form-control" id="captain">
</div>
<div class="col-md-6">
<label for="phone" class="form-label">Captain's phone</label>
<input type="tel" class="form-control" id="phone" inputmode="numeric">
</div>
<div class="col-md-6">
<label for="age" class="form-label">Age group</label>
<select class="form-select" id="age">
<option>Under 14</option>
<option>Under 17</option>
<option>Open</option>
</select>
</div>
<fieldset class="col-md-6">
<legend class="col-form-label pt-0">Match timing</legend>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timing" id="am" value="morning" checked>
<label class="form-check-label" for="am">Morning</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="timing" id="pm" value="evening">
<label class="form-check-label" for="pm">Evening</label>
</div>
</fieldset>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="pitch">
<label class="form-check-label" for="pitch">We need a practice pitch</label>
</div>
</div>
<div class="col-12">
<label for="players" class="form-label">Number of players</label>
<input type="range" class="form-range" min="11" max="15" step="1" id="players">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Register team</button>
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>The form itself is a row g-3, so each field is a grid column. The radios share name="timing" inside a fieldset, and the switch is a checkbox with form-switch and role="switch".
Quick Quiz
1. Which class should a <select> element use in Bootstrap 5?
form-select gives selects the matching arrow and padding; custom-select was Bootstrap 4.
2. What replaces Bootstrap 4's form-group wrapper?
Bootstrap 5 dropped form-group; spacing comes from utilities or grid gutters.
3. A field must show a value and send it with the form, but the user must not change it. Which attribute?
Disabled values are not submitted; readonly values are.
4. Why is a placeholder not a replacement for a label?
Labels stay visible and are linked to the field for assistive technology.
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
