FORM CONTROLS: Inputs, selects, checks, and input groups
VARIANTS: Controls | Input groups
Consolidated entry for form building blocks. Controls covers inputs, selects, labels, validation states, checks and switches; Input groups covers prefixed/suffixed and stepper composites.
FORM CONTROLS: Complete reference for all form input components
VARIANTS: Text Input | Select | Textarea | Checkbox | Radio | Toggle | Validation
Form controls are the primary way users enter data into your application. Use consistent styling, clear labels, and helpful validation feedback to guide users through forms efficiently.
Text Inputs: Basic single-line text entry fields. Use for names, emails, numbers, and other short text data.
Set by ApplicaaOne so schools can compare enquiries. Locked fields always explain why via helper text.
<input type="text" class="form-control" placeholder="..."><div class="input-group has-icon-end"><input class="form-control form-control-locked" readonly aria-describedby="..."><span class="input-group-icon input-group-icon-end"><i class="fa-regular fa-lock"></i></span></div>
Sizes: Three size options to match your UI density. Default size works for most cases.
<input class="form-control form-control-sm"><input class="form-control form-control-lg">
Labels: Every form field must have a visible label. Use required/optional indicators to communicate field requirements clearly.
<label class="form-label">Label</label><label class="form-label form-label-required">Required</label><label class="form-label form-label-optional">Optional</label>
Feedback: Use helper text to provide guidance. Validation states visually indicate success or errors with colored borders and feedback messages.
<input class="form-control is-valid"><input class="form-control is-invalid"><span class="form-text">Helper text</span><span class="valid-feedback">Success message</span><span class="invalid-feedback">Error message</span>
Select: Dropdown menus for choosing from a list of options. Use when there are more than 5 options or when options are dynamic.
Select Sizes
Select with Validation
<select class="form-select">...</select><select class="form-select form-select-sm">...</select>
Textarea: Multi-line text input for longer content like descriptions, comments, or messages.
Textarea Sizes
<textarea class="form-control" rows="3"></textarea>
Checkbox: Use for multiple selections where users can choose one or more options, or for boolean toggles like accepting terms.
Inline Checkboxes
Checkbox with Validation
Sizes (opt-in)
Sizes: .form-check-sm and .form-check-lg go on the
.form-check wrapper and scale the control box only — label typography stays with the caller.
The unsuffixed default is unchanged. Every size keeps a 24px row target (WCAG 2.5.8); only
.form-check-lg's control clears 24px on its own box, so prefer it on parent/applicant-facing forms.
<div class="form-check form-check-lg">
<div class="form-check"> <input class="form-check-input" type="checkbox" id="..."> <label class="form-check-label" for="...">Label</label></div>
Radio: Use for single selections where only one option can be chosen from a group.
Inline Radio Buttons
<input class="form-check-input" type="radio" name="group" id="...">
Toggle: Binary on/off controls for settings and preferences. Use instead of checkboxes when the action takes immediate effect.
Sizes (opt-in)
Switch sizes: the same .form-check-sm / .form-check-lg vocabulary
scales the switch track (28×14 · 31.5×17.5 · 42×24.5). .form-check-lg's track clears the 24px
target on its own.
<div class="form-switch form-check-lg">
<div class="form-switch"> <input class="form-check-input" type="checkbox" id="..."> <label class="form-check-label" for="...">Label</label></div>
Input Groups: Combine inputs with text addons, icons, or buttons for contextual input fields.
Input Group Sizes
<div class="input-group"> <span class="input-group-text">@</span> <input class="form-control"></div>
Plaintext: Display form values as plain text when in read-only mode while maintaining form layout alignment.
<input type="text" readonly class="form-control-plaintext" value="...">
Real-world examples: Typical form layouts and patterns you'll use in your application.
Login Form
Contact Form
Settings with Toggles
Date & Time Inputs: Native input[type=date] and input[type=time] are valid uses of .form-control. The DS border, padding, border-radius, and focus ring apply cleanly. No custom calendar component is needed — the browser's native picker is used.
Date + time pair: Use a tw:flex tw:gap-2 wrapper to place a date and time input side by side. Give the time input a fixed width (tw:w-32) and let the date input grow (tw:flex-1).
<input type="date" class="form-control tw:flex-1"><input type="time" class="form-control tw:w-32">
Quick Reference
Text Input: .form-control
Select: .form-select
Sizes: .form-control-sm | .form-control-lg | .form-select-sm | .form-select-lg
Validation: .is-valid | .is-invalid
Labels: .form-label | .form-label-required | .form-label-optional | .form-label-sm | .form-label-lg
Feedback: .form-text | .valid-feedback | .invalid-feedback
Checkbox/Radio: .form-check > .form-check-input + .form-check-label
Inline: .form-check-inline
Toggle: .form-switch > .form-check-input + .form-check-label
Input Group: .input-group > .input-group-text + .form-control
Input Group Sizes: .input-group-sm | .input-group-lg
Plaintext: .form-control-plaintext
Date & Time: input[type=date] | input[type=time] (both valid with .form-control)
States: disabled attribute | readonly attribute
INPUT GROUPS: Extend form controls with text, icons, buttons, and steppers
VARIANTS: Text Addons | Icons | Stepper | Buttons | Combined
Input groups allow you to attach additional context to form inputs through prefixes, suffixes, icons, and action buttons. Use them to improve clarity and reduce the number of separate form fields needed.
Text Addons: Attach static text labels to inputs for units, currencies, or contextual information.
<div class="input-group"> <span class="input-group-text">£</span> <input class="form-control"></div>
Icon Inputs: Add Font Awesome icons inside inputs for visual cues. Icons can be decorative or interactive (clearable, password toggle). The password examples below are wired to data-controller="password-reveal" — click the eye to toggle the field between masked and plain text.
Search with Icon
Email Input with Icon
Date Picker with Icon
Clearable Input (Interactive Icon)
Password with Visibility Toggle
Search with Both Icons
<div class="input-group has-icon-start"> <i class="input-group-icon input-group-icon-start fa-solid fa-magnifying-glass"></i> <input class="form-control"></div>
<div class="input-group has-icon-end" data-controller="password-reveal"> <input type="password" class="form-control" data-password-reveal-target="input"> <button type="button" class="input-group-icon input-group-icon-end input-group-icon-clickable" aria-label="Show password" aria-pressed="false" data-password-reveal-target="toggle" data-action="click->password-reveal#toggle"> <i class="fa-solid fa-eye" aria-hidden="true" data-password-reveal-target="icon"></i> </button></div>
Number Stepper: Attach up/down buttons to number inputs for easy incrementing/decrementing. Perfect for quantity selectors.
Basic Quantity Selector
Age with Stepper
Disabled Stepper
<div class="input-group" data-controller="stepper"> <input type="number" class="form-control" min="1" max="99" step="1" data-stepper-target="input" data-action="input->stepper#refresh change->stepper#commit"> <div class="input-group-stepper"> <button class="input-group-stepper-btn" aria-label="Increase quantity" data-stepper-target="increment" data-action="click->stepper#increment">▲</button> <button class="input-group-stepper-btn" aria-label="Decrease quantity" data-stepper-target="decrement" data-action="click->stepper#decrement">▼</button> </div></div>
Contract: min / max / step live on the
input, never duplicated as Stimulus values — the controller reads them on every operation.
The relevant button takes the native disabled attribute at each bound rather
than clamping silently or wrapping (same ends-behaviour as the Carousel arrows). Typing is
never fought mid-entry; on commit (blur / Enter / native spinner) an out-of-range value
clamps and non-numeric junk reverts, while an off-step but in-range number is
left alone and an empty field stays empty. aria-label must name the
field (“Increase quantity”), not just the direction — a bare
“Increase” button doesn't say what it increases. Dispatches
stepper:changed.
Button Addons: Attach action buttons directly to inputs for immediate actions like search, submit, or copy.
Search with Button
Email Subscription
Coupon Code
Copy to Clipboard
Multiple Buttons
<div class="input-group"> <input class="form-control"> <button class="btn btn-primary">Search</button></div>
Combined: Mix and match different addon types for complex input patterns.
Search: Icon + Button
Price: Currency + Stepper
Phone Number with Country
Password with Toggle + Button
Sizes: All input group components support small, default, and large sizes.
Small Size
Default Size
Large Size
<div class="input-group input-group-sm">...</div><div class="input-group input-group-lg">...</div>
Validation: Input groups work seamlessly with validation states.
Real-world examples: Typical input group combinations used in applications.
Search Bar
Product Cart Item
£29.99
Price Filter
Quick Reference
Container: .input-group
Text Addon: .input-group-text
Icon: .input-group-icon | .input-group-icon-start | .input-group-icon-end | .input-group-icon-clickable
Icon Modifiers: .has-icon-start | .has-icon-end (add to .input-group)
Stepper: .input-group-stepper > .input-group-stepper-btn
Button: .input-group > .btn
Sizes: .input-group-sm | .input-group-lg
Note: Use Font Awesome icons (.fa-solid, .fa-regular, etc.)
Password reveal: data-controller="password-reveal" on the .input-group; no new CSS, reuses .input-group-icon-clickable