Form Design

Forms are where users do actual work: sign up, check out, submit data, configure settings. A well-designed form gets out of the way. A poorly designed one causes abandonment, errors, and support tickets. Form design has more research behind it than almost any other UX topic.

Form Layout Best Practices

Single-Column Layout

Single-column forms have the highest completion rates. The eye moves straight down without jumping between columns.

BEST (single column):               AVOID (multi-column):
┌───────────────────────┐            ┌───────────────────────────┐
│ First name            │            │ First name  │ Last name   │
│ [_______________]     │            │ [________]  │ [________]  │
│                       │            │                           │
│ Last name             │            │ Email       │ Phone       │
│ [_______________]     │            │ [________]  │ [________]  │
│                       │            │                           │
│ Email                 │            │ City   │ State │ Zip      │
│ [_______________]     │            │ [_____]│ [___] │ [____]   │
└───────────────────────┘            └───────────────────────────┘
Completion rate: higher              Completion rate: lower

Exception: Short, related fields can share a row: City / State / Zip, or First name / Last name, but only if they're logically grouped and the form remains scannable.

Label Placement

PlacementSpeedDensityBest For
Above fieldFastest completionLower (more vertical space)Most forms, mobile, accessibility
Left of fieldSlower (eye zigzags)Higher (less vertical space)Data-heavy desktop forms where density matters
Floating labelFast (once understood)ModerateSign-in, simple forms (but has accessibility concerns)
Inside field (placeholder only)Worst (label disappears)HighestNever use as the only label

Recommendation: Labels above fields is the safest default. It works on all screen sizes, meets accessibility requirements, and has the fastest completion times in research studies.

Form Structure

┌─────────────────────────────────────────────┐
│ Section Title (e.g., "Personal Information")│
│                                             │
│ Label                           ← Visible label
│ ┌─────────────────────────────┐ ← Clear input boundary
│ │ Placeholder text            │ ← Example, not label
│ └─────────────────────────────┘
│ Helper text appears here       ← Optional guidance
│                                             │
│ Label *                         ← Required indicator
│ ┌─────────────────────────────┐
│ │                             │
│ └─────────────────────────────┘
│ ✗ Error message in red         ← Inline error
│                                             │
│            [Continue →]         ← Clear action
└─────────────────────────────────────────────┘

Input Types

Always use the correct HTML5 input type. It determines the mobile keyboard, enables browser autofill, and provides free validation.

DataHTMLMobile KeyboardAdditional Attributes
Emailtype="email"@ key visibleautocomplete="email"
Phonetype="tel"Numeric padautocomplete="tel"
Credit cardinputmode="numeric"Numeric padautocomplete="cc-number"
Amount/priceinputmode="decimal"Numeric with decimalNone
PIN/OTPinputmode="numeric"Numeric padautocomplete="one-time-code"
URLtype="url".com and / keysautocomplete="url"
Searchtype="search"Search key + clearNone
Datetype="date"Native date pickerNone
Passwordtype="password"Obscuredautocomplete="new-password" or autocomplete="current-password"

Choosing the Right Control

QuestionOptions ≤ 5Options 6-15Options 15+
Select oneRadio buttonsSelect dropdownSelect with search
Select multipleCheckboxesCheckbox listMulti-select with search
Yes/NoToggle or checkboxN/AN/A
Free text (short)Text inputN/AN/A
Free text (long)TextareaN/AN/A

Rule: If there are fewer than 5 options, show them all (radio/checkbox). Don't hide them behind a dropdown.

Validation

When to Validate

TimingHow It WorksBest For
On blur (recommended)Validates when user leaves the fieldMost fields. Gives user time to finish typing
On submitValidates all fields when form is submittedFallback when on-blur isn't implemented
On input (real-time)Validates as user types, keystroke by keystrokePassword strength, character counters, search
On focus (re-validation)Re-validate previously errored field when user returnsPreviously invalid fields

Validation Strategy

1. User fills in field → No validation yet (let them type)
2. User leaves field (blur) → Validate this field
3. If valid → Show subtle green check (optional)
4. If invalid → Show inline error message
5. User returns to fix → Re-validate on every change (on input)
6. When fixed → Immediately show success state
7. User submits → Validate all fields, scroll to first error

Never validate:

  • While the user is still actively typing (except password strength)
  • Before the user has interacted with the field at all
  • By clearing the field content on error

Inline Validation Example

DURING TYPING (no validation feedback):
Email
┌─────────────────────────┐
│ jane@exam                │
└─────────────────────────┘

AFTER BLUR (valid):
Email
┌─────────────────────────┐ ✓
│ jane@example.com         │
└─────────────────────────┘

AFTER BLUR (invalid):
Email
┌─────────────────────────┐ ✗
│ jane@exam                │  ← Red border
└─────────────────────────┘
Please enter a complete email address (e.g., name@example.com)

Error Messages

Anatomy of a Good Error Message

Every error message should contain:

  1. What went wrong: stated clearly
  2. How to fix it: actionable instruction
  3. An example: when the format isn't obvious
Bad ErrorGood Error
"Invalid input""Enter a valid email (e.g., name@example.com)"
"Error""Password must be at least 8 characters"
"Error code: VAL_001""This email is already registered. [Sign in instead?]"
"Field required""Enter your phone number to receive delivery updates"
"Invalid date""Enter a date in MM/DD/YYYY format"
"Password error""Passwords don't match. Re-enter your password"

Error Display Guidelines

GuidelineWhy
Show error inline, directly below the fieldUser can see the error without scrolling
Use red border + red text + error iconRedundant signals (not just color) for accessibility
Don't use alerts/modals for validation errorsDisruptive and forces dismissal before fixing
Scroll to and focus the first error on submitUser knows exactly where to start fixing
Don't clear the field on errorUser loses their input and has to retype everything
Preserve error until fixedDon't remove the error when user starts typing elsewhere
Use aria-describedby to link error to fieldScreen readers announce the error message
<label for="email">Email address</label>
<input
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid="true"
  class="input-error"
>
<span id="email-error" class="error-message" role="alert">
  Please enter a valid email address (e.g., name@example.com)
</span>

Password Fields

Password Creation UX

Show requirements as the user types with real-time feedback:

Password
┌─────────────────────────────────────┐
│ ●●●●●●●●●●                         │ [👁 Show]
└─────────────────────────────────────┘
Password strength: ████████░░ Strong

Requirements:
✓ At least 8 characters
✓ One uppercase letter
✓ One lowercase letter
✗ One number
✗ One special character (!@#$%)

Password Best Practices

PracticeWhy
Show/hide toggleUsers need to verify what they typed, especially on mobile
Show strength meterMore motivating than a list of rules
Don't require periodic changesNIST recommends against it; it causes weaker passwords
Allow paste into password fieldsPassword managers need this
Check against breached password listsHave I Been Pwned API
Use autocomplete="new-password"Triggers password manager to suggest a strong password

Smart Defaults and Auto-Complete

Reduce effort by pre-filling what you can:

TechniqueImplementationImpact
Browser autofillUse correct autocomplete attributesFills name, email, address, card in seconds
Geolocation defaultsDetect country/timezone from IPSkip country selection for most users
Previous valuesRemember last-used settingsReturning users complete forms faster
Smart detectionDetect card type from number, format phone as typedFewer errors, less thinking
Pre-selected defaultsCheck the most common option by default80% of users won't need to change it

Essential Autocomplete Values

<input autocomplete="given-name">       <!-- First name -->
<input autocomplete="family-name">      <!-- Last name -->
<input autocomplete="email">            <!-- Email -->
<input autocomplete="tel">              <!-- Phone -->
<input autocomplete="street-address">   <!-- Address line -->
<input autocomplete="address-level2">   <!-- City -->
<input autocomplete="address-level1">   <!-- State/Province -->
<input autocomplete="postal-code">      <!-- ZIP/Postal code -->
<input autocomplete="country">          <!-- Country -->
<input autocomplete="cc-number">        <!-- Credit card number -->
<input autocomplete="cc-exp">           <!-- Card expiry -->
<input autocomplete="cc-csc">           <!-- Card CVC -->

Progressive Disclosure

Only show fields when they're relevant. This reduces cognitive load and makes the form feel shorter.

BEFORE (all fields visible):
┌─────────────────────────────────────────┐
│ Shipping address                        │
│ [___________________________________]  │
│ Billing address                         │
│ [___________________________________]  │
│ Company name (if applicable)            │
│ [___________________________________]  │
│ VAT number (if applicable)              │
│ [___________________________________]  │
│ Gift message (if applicable)            │
│ [___________________________________]  │
└─────────────────────────────────────────┘

AFTER (progressive disclosure):
┌─────────────────────────────────────────┐
│ Shipping address                        │
│ [___________________________________]  │
│                                         │
│ ☑ Billing address same as shipping      │
│                                         │
│ ☐ This is a business purchase           │
│   (shows Company + VAT fields if checked) │
│                                         │
│ ☐ Add a gift message                    │
│   (shows message field if checked)      │
└─────────────────────────────────────────┘

Multi-Step Forms

When to Split a Form

  • The form has more than 6-8 fields
  • Fields fall into distinct logical groups
  • Some fields depend on earlier answers
  • The form has a high abandonment rate

Multi-Step Form Design

Step 1 of 4         Step 2 of 4         Step 3 of 4         Step 4 of 4
Account        →    Personal       →    Preferences    →    Review
  ●────────────────○────────────────○────────────────○
GuidelineWhy
Show total steps and current positionSets expectations, reduces anxiety
Allow backward navigationUsers need to review and change earlier answers
Save progress automaticallyUsers may abandon and return later
Summarize on final stepLets users verify before submitting
Validate each step before advancingDon't let errors accumulate to the end
Keep steps roughly equal in lengthOne 12-field step + three 2-field steps feels unbalanced
Use descriptive step labels"Personal Info" not "Step 1"

Multi-Step Progress Indicators

STYLE 1: Connected dots with labels
  ● Account ─── ○ Personal ─── ○ Payment ─── ○ Review
  (completed)    (current)      (upcoming)    (upcoming)

STYLE 2: Numbered steps
  ✓ 1 ─── ● 2 ─── ○ 3 ─── ○ 4

STYLE 3: Progress bar
  ████████████████░░░░░░░░░░░░░░  Step 2 of 4

Required vs. Optional Fields

Two schools of thought:

ApproachWhen to Use
Mark optional fields with "(optional)"When most fields are required
Mark required fields with * and legendWhen many fields are optional

Preferred approach: Make every field required unless there's a good reason not to. Then mark the few optional ones with "(optional)" in the label. This reduces the number of markers needed and reduces form length overall (remove fields you don't need).

Full name
[_______________]

Email
[_______________]

Phone (optional)
[_______________]

Message
[_______________]

Accessible Form Patterns

RequirementImplementation
Every input has a visible label<label for="field-id"> linked to input
Error messages linked to fieldsaria-describedby="error-id" on the input
Required fields announcedaria-required="true" or HTML required
Invalid state announcedaria-invalid="true" when validation fails
Form errors announcedError summary with role="alert" or aria-live="polite"
Fieldsets for groups<fieldset> + <legend> for radio/checkbox groups
Visible focus indicators2px+ focus ring on all interactive elements

Common Mistakes

MistakeImpactFix
Placeholder as only labelLabel disappears when typing, fails accessibilityAlways use a visible <label> above the field
Clearing fields on errorUser loses all input, rage-inducingPreserve input and highlight the error
Validating while typingError appears mid-word ("Invalid email" after typing "j")Validate on blur, re-validate on input for errored fields
No error summary on submitUser can't find the errorsScroll to first error and show an error count summary
Generic error messagesUser can't fix the problemBe specific: what's wrong + how to fix it + example
Too many fieldsAbandonment increases with every fieldRemove every field that isn't essential. Can you get it later?
Missing autocomplete attributesUser types everything manuallyAdd autocomplete to every personal data field
Multi-column layoutBreaks scanning, causes errorsUse single-column for most forms

Key Takeaways

  • Single-column, labels above fields, validate on blur. These three decisions alone fix most form problems.
  • Every error message needs: what's wrong, how to fix it, and (ideally) an example.
  • Use the right input type and autocomplete attributes. They give you free mobile keyboards and autofill.
  • Show fewer than 5 options as radio/checkboxes, not dropdowns. Make choices visible.
  • Use progressive disclosure to hide conditional fields until they're relevant.
  • Split forms with more than 6-8 fields into multi-step forms with clear progress indicators.
  • Remove every field that isn't absolutely necessary. Every field you remove increases completion rates.
  • Test forms with real users completing real tasks. Form problems only surface during actual use.