Patterns

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.

Pattern ws-field ws-input ws-select ws-form-section

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.

WS
Woodruff Sawyer / New quote
Auto-saved 2 min ago AJ

Coverage details

Specify the type of coverage, requested limits, deductibles, and effective dates. Fields marked * are required.

Complete 1 · Client information
Named insured
TechFlow Systems Inc.
DBA
TechFlow
Entity type
Corporation
State of incorporation
Delaware
Primary contact
Marcus Webb
Contact email
mwebb@techflow.io
2 · Coverage details

Select the line of business and specify the coverage parameters the underwriter will quote against.

Choose the primary line of business for this quote.
Must be at least 3 business days from today.
Defaults to 12 months from effective date.
Describe any non-standard requirements. Underwriters review this field before binding.
Locked 3 · Risk information
Complete section 2 to unlock
<!-- 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

1
Section navigation sidebar

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.

2
Progress bar

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.

3
Completed section (collapsed read-only)

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.

4
Active section card

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).

5
Locked section preview

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.

6
Sticky footer action bar

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 classColumnsUse when
ws-field-row--22 × 50%Most text inputs and selects. Pairs naturally: first name / last name, coverage type / carrier, effective / expiration.
ws-field-row--33 × 33%Short related fields that form a logical triple: limit / aggregate / deductible; city / state / ZIP.
ws-field-row--auto2fr / 1fr / 120pxAddress 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.
Max content width: 720 px

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.

A coverage limit is required before you can continue.
Effective date must be at least 3 business days from today (minimum: Aug 1, 2026).
<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>
TriggerBehavior
User submits / clicks Continue with missing required fieldAdd 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 awayRemove is-error immediately on blur if the value is now valid — don't wait for re-submit.
Server-side validation failure on ContinueSame as above, but additionally show a ws-alert--danger at the top of the section summarising all field errors in one sentence.

Component inventory

ComponentWhere usedClassDocumentation
InputText fields (date, freeform text)ws-inputForms & inputs
SelectDropdowns (coverage type, carrier, limit)ws-selectForms & inputs
TextareaLong-form notes fieldws-textareaForms & inputs
Field wrapperEvery fieldws-field, ws-field.is-errorForms & inputs
ButtonContinue, Save draft, Back, Editws-btnButtons
BadgeSection status (Complete, Locked), auto-savews-badgeFeedback & status
Progress barSection completionws-progressFeedback & status
AlertServer validation errorsws-alert ws-alert--dangerFeedback & status
BreadcrumbPage header wayfindingws-breadcrumbNavigation

Responsive behavior

BreakpointChange
< 960 pxSection sidebar hides; replace with a horizontal step indicator (compact dots or numbered chips) beneath the topbar.
< 768 pxAll ws-field-row--2 and ws-field-row--3 collapse to single-column. Every field stacks vertically.
< 480 pxFooter action bar stacks vertically (Continue on top, Save draft / Back below). The Continue button spans full width.

Accessibility

ElementRequirement
Section navigationrole="navigation" with aria-label="Form sections". Active section gets aria-current="step".
Required fieldsDo not rely solely on the asterisk *. Add required to the native input / select so browsers and assistive technology enforce it.
Error messagesUse aria-invalid="true" and aria-describedby pointing to the error message element so screen readers associate the message with the field.
Focus managementOn Continue with errors, move focus to the first invalid field — not the error summary — so keyboard users can correct the problem immediately.
Locked sectionsAdd aria-disabled="true" and tabindex="-1" to locked section nav items. Include a tooltip or helper text explaining why they're locked.
Progress barAdd role="progressbar", aria-valuenow, aria-valuemin="0", aria-valuemax="100", and aria-label="Form progress".
Auto-save prevents data loss

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.