Form layout
A multi-section application and quote-intake form assembled from ws-* components — covering field layout, grouping strategy, progressive disclosure, inline validation, and the save-and-continue footer action pattern.
Overview
Long insurance application forms are among the most error-prone surfaces in any product. This pattern addresses that with three structural choices: section cards group related fields so users know exactly how much they have left; a persistent side-nav makes the overall form structure visible at all times; and a sticky footer keeps Save and Continue always reachable without scrolling.
Fields within each section follow the 8-column grid. Simple text fields span 4 columns (half-width). Short fields — state, ZIP, date — span 2 columns. The page content column never exceeds 720 px to keep line lengths comfortable.
Live pattern
A complete quote intake form — three sections, one complete, one in progress, one locked. All fields use ws-field, ws-input, and ws-select. The validation state on the address field uses ws-field is-error.
Coverage details
Specify the type of coverage, requested limits, deductibles, and effective dates. Fields marked * are required.
Select the line of business and specify the coverage parameters the underwriter will quote against.
<!-- Form shell -->
<div class="pat-form-sidenav">
<!-- Step nav with .is-done / .is-active / .is-locked -->
<div class="ws-progress"><div class="ws-progress__bar" style="width:40%"></div></div>
</div>
<!-- Completed section (read-only, collapsed) -->
<div class="ws-form-section">…</div>
<!-- Active section -->
<div class="ws-form-section">
<div class="ws-form-section__header">
<div class="ws-form-section__title">2 · Coverage details</div>
</div>
<div class="ws-field-row ws-field-row--2">
<div class="ws-field">
<label class="ws-field__label">Coverage type</label>
<select class="ws-select">…</select>
</div>
</div>
</div>
<!-- Sticky footer -->
<div class="ws-form-footer">
<div class="ws-form-footer__left">
<button class="ws-btn ws-btn--secondary">Save draft</button>
<button class="ws-btn ws-btn--tertiary">Back</button>
</div>
<div class="ws-form-footer__right">
<button class="ws-btn ws-btn--primary">Continue</button>
</div>
</div>Anatomy
Lists every section with a completion indicator (empty circle = locked, filled check = done, highlighted = active). Always visible so users know exactly how much remains and can jump back to completed sections.
Reinforces the sidebar with a linear progress bar showing the percentage of sections complete. Never show as a percentage number alone — the section list is more informative.
Once a section is completed and the user moves forward, it collapses into a summary card showing key values in a 3-column grid. An "Edit" button is always visible so they can return to change details without losing progress in later sections.
The current section's fields in a ws-form-section card. Fields use ws-field-row--2 (half-width) for most text and selects, and ws-field-row--3 when three short fields share a logical group (e.g. limit, aggregate, deductible).
Future sections render at reduced opacity with a "locked" badge. This gives users a preview of what's coming without letting them skip ahead before prerequisite data is entered.
Save draft (left) and Continue / Back (right) are always on-screen via position: sticky. Never float the primary action outside the viewport — on a 900px-tall field-heavy section, users must not scroll to find "Continue".
Field layout rules
Consistent column widths reduce visual noise and help users read forms faster. Follow this decision tree for every field row.
| Field row class | Columns | Use when |
|---|---|---|
ws-field-row--2 | 2 × 50% | Most text inputs and selects. Pairs naturally: first name / last name, coverage type / carrier, effective / expiration. |
ws-field-row--3 | 3 × 33% | Short related fields that form a logical triple: limit / aggregate / deductible; city / state / ZIP. |
ws-field-row--auto | 2fr / 1fr / 120px | Address rows where street address is wider than city, which is wider than state abbreviation or ZIP. |
| Single column (default) | 1 × 100% | Textareas, rich text fields, and any field where more horizontal space improves comprehension of the placeholder or label. |
The form content column inside the shell is intentionally constrained. Do not widen it in the name of "using the space" — wider fields produce longer line scans and more eye travel on label-to-field pairs. Keep the grid tight.
Validation states
Always validate inline — never redirect users to a separate error summary page. Use ws-field is-error on the field wrapper to trigger the red border and error message.
<div class="ws-field is-error">
<label class="ws-field__label" for="cov-limit">
Per-occurrence limit <span>*</span>
</label>
<select id="cov-limit" class="ws-select"
aria-invalid="true"
aria-describedby="cov-limit-err">…</select>
<span class="ws-field__error" id="cov-limit-err">
<svg>…</svg>
A coverage limit is required before you can continue.
</span>
</div>| Trigger | Behavior |
|---|---|
| User submits / clicks Continue with missing required field | Add is-error to .ws-field, reveal .ws-field__error, set aria-invalid="true", scroll to first error, move focus to first invalid input. |
| User corrects the field and moves focus away | Remove is-error immediately on blur if the value is now valid — don't wait for re-submit. |
| Server-side validation failure on Continue | Same as above, but additionally show a ws-alert--danger at the top of the section summarising all field errors in one sentence. |
Component inventory
| Component | Where used | Class | Documentation |
|---|---|---|---|
| Input | Text fields (date, freeform text) | ws-input | Forms & inputs |
| Select | Dropdowns (coverage type, carrier, limit) | ws-select | Forms & inputs |
| Textarea | Long-form notes field | ws-textarea | Forms & inputs |
| Field wrapper | Every field | ws-field, ws-field.is-error | Forms & inputs |
| Button | Continue, Save draft, Back, Edit | ws-btn | Buttons |
| Badge | Section status (Complete, Locked), auto-save | ws-badge | Feedback & status |
| Progress bar | Section completion | ws-progress | Feedback & status |
| Alert | Server validation errors | ws-alert ws-alert--danger | Feedback & status |
| Breadcrumb | Page header wayfinding | ws-breadcrumb | Navigation |
Responsive behavior
| Breakpoint | Change |
|---|---|
| < 960 px | Section sidebar hides; replace with a horizontal step indicator (compact dots or numbered chips) beneath the topbar. |
| < 768 px | All ws-field-row--2 and ws-field-row--3 collapse to single-column. Every field stacks vertically. |
| < 480 px | Footer action bar stacks vertically (Continue on top, Save draft / Back below). The Continue button spans full width. |
Accessibility
| Element | Requirement |
|---|---|
| Section navigation | role="navigation" with aria-label="Form sections". Active section gets aria-current="step". |
| Required fields | Do not rely solely on the asterisk *. Add required to the native input / select so browsers and assistive technology enforce it. |
| Error messages | Use aria-invalid="true" and aria-describedby pointing to the error message element so screen readers associate the message with the field. |
| Focus management | On Continue with errors, move focus to the first invalid field — not the error summary — so keyboard users can correct the problem immediately. |
| Locked sections | Add aria-disabled="true" and tabindex="-1" to locked section nav items. Include a tooltip or helper text explaining why they're locked. |
| Progress bar | Add role="progressbar", aria-valuenow, aria-valuemin="0", aria-valuemax="100", and aria-label="Form progress". |
Persist form state to the server every 60 seconds and on every section transition. Display the last-saved time in the topbar. This is critical for long quote forms where a session timeout or accidental navigation can erase 20 minutes of work.