Design System

Forms

Where operators configure behavior.
FIELD ANATOMY · top label · field · hint · error CALLSIGN BLU-2 Single word, uppercase, no spaces. SIDC · REQUIRED 32TWN... ! Must be a 10-digit MGRS grid reference. STATES · at-rest · hover · focus · disabled · filled at rest hover BLU-2 disabled SELECT · RADIO · CHECKBOX · NUMBER Fortress AI VISUAL THERMAL SIGNALS Confirm before engage 1500 SWITCH · SLIDER · FOOTER ACTIONS Auto-engage RADIUS · 1500 m CANCEL BRIEF MISSION

Layout

Single-column is the default. Multi-column only when:

  1. Fields are logically paired (min/max, start/end, from/to), or
  2. The form has 15+ fields and vertical scrolling becomes operator-hostile.

Column width

single-column forms cap at 40ch (~320px) - readable one-line width. Two-column layouts use two 30ch columns with a 24px gap between.

Field density

standard = 16px vertical gap between fields. Compact (operator-dense internal admin) = 8px. Never smaller; at that tier, structural grouping is cleaner than further tightening.

Labels

Labels are always top-aligned and always present. Placeholder text is not a label - it disappears when the user types and fails accessibility.

  • Position: above the field, 4px gap
  • Type: Inter 500 13px, fog-100
  • Required fields: append a steel-300 asterisk * after the label text. Do not color the asterisk red - it’s not an error state, it’s a requirement. Don’t append “(required)” either - the asterisk is the convention.
  • Optional fields: no indicator. The default assumption is optional; required is the marked case.

Side-aligned labels (label to the left of the field) are permitted only for dense internal admin forms where vertical space is at a premium. Never on operator-facing surfaces.

Help text

  • Position: below the field, 4px gap
  • Type: Inter 400 12px, steel-300
  • Length: one line preferred, two lines maximum
  • Purpose: disambiguation, format hint (UTC timestamp, YYYY-MM-DDTHH:MM:SSZ), or field-specific guidance. Not marketing copy, not elaboration on why the field exists.

Validation

Timing

  • Inline validation fires on blur, not on every keystroke. Fighting the user as they type is hostile.
  • Exception: password-strength meters and character counters, which update on input because that IS the feedback.
  • Submission validation runs on submit and focuses the first invalid field.

Display

  • Inline error - below the field, replacing the help text. hostile-400 text, Inter 400 12px. triangle-exclamation icon 12px inline left of the error text.
  • Field state - invalid field has a 2px hostile-400 border (replacing the default navy-600) plus --fdt-glow-xs in hostile tint.
  • Summary at top of form - only when 3+ fields are invalid. Uses wa-callout variant="danger". Lists field labels as anchor links that scroll/focus to the field on click.

Error copy

State what’s wrong and how to fix, in that order. Never just “required.” The tone is doctrinal and declarative - never apologetic, never conversational.

Do Don’t
Enter a mission code - 3 to 12 uppercase characters Oops! Please enter a valid mission code
Date must be in the future That date can't be right
Password must contain at least one number Weak password
Invalid MGRS grid reference That doesn't look like an MGRS grid
Value exceeds maximum of 1,000 Value is too high

Never use “please.” This is a console, not a service request.

Field sizing

Field widths match the data they accept. A phone number field does not need to be full-column wide.

Field type Width
Short identifiers (codes, IDs) 8–12 characters
Small numbers (<4 digits) 6 characters
Coordinates (pair) Two 12-character fields side-by-side
Dates / times date picker + 10–19 character text fallback
Email address full column
Name full column
URL full column
Free-text comment / description full column, multi-line

Making every field full-column wastes space and implies the data is more open-ended than it is.

Sections and grouping

For forms with 8+ fields, group related fields into sections:

  • Section heading: H3, Inter 600 14px UC tracked 0.08em, fog-100, with a navy-600 1px underline
  • Section gap: 32px above, 16px below the heading
  • No visible fieldset border - the heading and spacing do the work

For complex multi-section forms (mission authoring, program creation, user setup), consider a multi-step wizard instead of one long form.

Multi-step forms (wizards)

When a form has 5+ logical sections or takes more than ~2 minutes to complete:

  • Step indicator at the top - horizontal steps with current / complete / upcoming states. JetBrains Mono 10px step number + Inter 500 11px label.
  • Per-step validation on Next - can’t advance past an invalid step.
  • Back / Next / Save as draft buttons at the bottom, right-aligned. Save as draft is optional but encouraged for forms users will return to.
  • Never auto-advance - the user hits Next explicitly, always. An auto-advancing wizard feels like it’s racing the operator.
  • Progress persists - if the user refreshes or navigates away, the wizard resumes at the step they left, with the partially-filled data intact.

Submit actions

  • Primary submit: right-aligned at the bottom of the form, wa-button variant="brand", label matches the action - Create mission, Save changes, Send invite. Never Submit.
  • Secondary (cancel): left of primary, wa-button variant="neutral" appearance="plain", label Cancel or Discard.
  • Destructive secondary (e.g., Delete user): far left, wa-button variant="danger" appearance="plain", separated from the other actions by a flex gap.
  • Never full-width submit buttons outside of QRF mobile. Full-width submit is a consumer-app pattern.

Dirty-form warnings

If the user navigates away from a form with unsaved changes:

  • Intercept the navigation attempt
  • Show a wa-dialog with: “You have unsaved changes to {form name}. Discard them?”
  • Primary action: Keep editing (stays on form, brand variant)
  • Secondary action: Discard changes (leaves form, danger variant)
  • Do not offer a Save option in the dialog - saving is a different action with its own button on the form. The dialog’s job is to protect against accidental loss, not to become a mini-form.

Autofocus

  • Autofocus the first empty or invalid field on mount
  • Do not autofocus if the form is embedded in a page with controls above it (classification strip, primary nav) - autofocus on mount can trap screen-reader users
  • Do not autofocus on mobile - triggers the keyboard immediately, often obscuring form context

Accessibility

  • Every field has a <label> associated via for/id, not just a visible text element
  • Required fields: required attribute + asterisk in label + aria-required="true"
  • Error state: aria-invalid="true" + aria-describedby pointing to the error text element
  • Groups: <fieldset> + <legend> for radio groups and related checkbox sets
  • Keyboard: Tab traversal follows visual order; Shift+Tab reverses; Enter submits the form (unless focus is inside a textarea)
  • Focus visible: the two-layer focus ring (2px electric-500 border + --fdt-glow-sm)