Skip to content

Forms Portal — Design Spec

Classification: CONFIDENTIAL — Internal Use Only Document: architecture/forms-frontend-design-spec.md · v1.0 · 2026-04-24 · GPUS-IT Companion to: architecture/forms-api-contract.md v1.3


Purpose

Pins every visual decision for gpus-forms-frontend so future edits — by anyone, including future Claude sessions — stay coherent. If a design tradeoff isn't covered here, it's out of spec and should be proposed as a revision before shipping.


Aesthetic direction

Editorial field report. The portal is designed to feel like filling out an official Greenpeace document, not a generic SaaS form. Submissions carry weight — they create HappyFox tickets, route to department groups, land in reports — and the design reflects that.

Reference points: - Editorial layouts (The New Yorker, Field Notes notebooks, boarding passes) - Institutional forms that don't apologize for being forms (IRS 1040, old typewritten field reports) - The existing GPUS-IT estate (dark slate MkDocs, monospace metadata, operational status site)

Explicit non-goals: - Marketing/product energy — no gradient buttons, no emoji, no "Let's get started!" tone - Skeuomorphic decoration — no paper textures, no ribbon borders, no fake folds - Dense admin-tool density (B from the exploration) — editorial needs breathing room


Color tokens

Token Hex Use
--panel-dark #1A2618 Long-form left panel background; primary submit button
--panel-dark-ink #E8EFE2 Text on --panel-dark
--surface-warm #FAF8F0 Form pane background (warm cream, not stark white)
--surface-0 #F5F4EF Page background (slightly cooler than the form pane)
--ink-1 #1A1A17 Primary text
--ink-2 #4A4A44 Secondary text, descriptions
--ink-3 #8A8A82 Tertiary, monospace metadata labels
--ink-4 #B8B8AF Placeholder text, disabled state
--rule #1A2618 Form field underline, matches panel
--rule-faint #E0DDD0 Horizontal dividers, card borders on warm surface
--accent-success #00BE51 Greenpeace green — success states only (submitted pill, ticket link, email-sent indicator, focus ring)
--accent-success-bg #E8F9EF Tinted backgrounds for success pills
--danger #C53030 Form validation errors, failed submissions
--danger-bg #FDF2F2 Error banner background
--warning #B87333 Queued / retrying states (e.g., HappyFox DLQ)
--warning-bg #FDF3E8 Warning pill background

Greenpeace green rule: reserved for communicating success or active state. Never on primary CTAs (submit, continue, next). This keeps the green meaningful — when a user sees it, it means something completed successfully.


Typography

Three typefaces, each with a specific job:

Token Family Use
--font-display 'Fraunces', 'Iowan Old Style', Georgia, serif Form titles, section headings, italic subtitles
--font-body 'Inter Tight', system-ui, sans-serif Form field inputs, body copy, buttons
--font-mono 'JetBrains Mono', 'SF Mono', Menlo, monospace Metadata (field labels, section markers §, doc numbers, status indicators)

Why Fraunces: variable serif with editorial warmth. Not precious like Didot, not generic like Georgia. Supports a SOFT optical size that reads as human.

Size scale:

Role Size Weight Family Example
Form title 28px 500 display "Welcome aboard."
Italic subtitle 13px 400 italic display "A few details so we can…"
Section heading number 9px (tracking 0.1em) 400 mono § 01
Section heading name 11px 500 body Identity
Field label 9px (tracking 0.1em, UPPERCASE) 400 mono LEGAL NAME *
Field input 14px 400 body user typing
Metadata (doc number, status) 10–11px (tracking 0.05–0.12em) 400 mono DOC · GP-HR-0421
Buttons 10px (tracking 0.08em, UPPERCASE) 500 mono SUBMIT →

Never used: font weights outside 400 and 500. No bold (700). No thin (300). Editorial restraint means modest weight range.


Layout: long form vs. short form

Forms fork based on section count.

Long form (5+ sections)

Two-pane layout: 180px left panel + flex form pane.

┌─────────┬──────────────────────────────────┐
│         │  01 · INTAKE FORM                 │
│  GREEN  │                                   │
│  PEACE  │  Welcome aboard.                  │
│         │  (italic subtitle)                │
│  USA ·  │                                   │
│  FORMS  │  § 01  Identity                   │
│         │  ─────────────────                │
│  DOC ·  │  LEGAL NAME *     PREFERRED NAME  │
│  GP-HR  │  [input]          [input]         │
│  -0421  │                                   │
│  v1     │  § 02  Equipment                  │
│         │  ─────────────────                │
│         │  ...                              │
│         │                                   │
│ "quote" │  ───────────────────────────────  │
│ § handbk│  3 OF 7  · DRAFT    [Cancel] [Submit →]
└─────────┴──────────────────────────────────┘

Left panel content (top to bottom): - GREENPEACE wordmark (mono, 10px, letter-spacing 0.12em, opacity 0.75) - USA · FORMS subhead (mono, 10px, opacity 0.45) - DOC · <doc_number> after a 24px gap - v<n> · <YYYY-MM-DD> version line under the doc number - Flex-grow spacer - Italic serif quote (12px, opacity 0.75) at the bottom - § <source> attribution under the quote (mono, 9px, opacity 0.45)

Quote source: per-form config in the admin form definition. Short pull-quotes from Greenpeace publications, the crew handbook, or org mission. Admin leaves it blank → a sensible default rotates in.

Short form (< 5 sections)

Single pane. Left panel is hidden. Doc metadata moves to the top-right of the form header.

┌──────────────────────────────────────────┐
│  02 · ACCESS REQUEST       DOC · GP-FAC-0213
│                            v2 · 2026-03-12 │
│  Parking pass request.                     │
│                                            │
│  § 01  Request                             │
│  ─────────────────                         │
│  VEHICLE PLATE *    START DATE *           │
│  [input]            [input]                │
│                                            │
│  ───────────────────────────────────────   │
│  0 OF 2 · DRAFT   [Cancel]  [Submit →]     │
└──────────────────────────────────────────┘

Fork decision lives in the form schemafields grouped into sections metadata. If sections.length >= 5, render long-form. Otherwise short-form.

Phase 2 note: Phase 1 schema doesn't have explicit sections yet — fields are flat. Phase 2.2 adds sections as a grouping metadata field. Until then, the SPA treats all forms as short-form (single pane). The quote-panel renders only when both sections.length >= 5 AND form.quote is non-empty.


Spacing

  • Form pane padding: 2rem 2.25rem (32px top/bottom, 36px sides)
  • Vertical rhythm between fields: 20px
  • Horizontal gap between two-column fields: 26px
  • Space after section heading: 14px
  • Section separator: 1.5rem bottom margin after each section's fields
  • Footer border-top: 0.5px solid var(--rule-faint) with 1rem padding above

Field underlines, not boxes: form inputs use border-bottom: 0.5px solid var(--rule) only. No full-box borders. This is deliberate — editorial forms use underline-only fields, and it helps the page feel like a document rather than a web UI. Focus state: underline thickens to 1px and shifts to --accent-success (green) as the focus signal.


Component specs

Form title

.form-title {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--ink-1);
  margin: 0 0 8px;
}

Title always ends with a period ("Welcome aboard." / "Parking pass request."). Editorial convention — declarative, not promotional.

Italic subtitle

.form-subtitle {
  font-family: var(--font-display);
  font-size: 13px;
  font-style: italic;
  color: var(--ink-2);
  line-height: 1.5;
  margin-bottom: 1.5rem;
}

One or two short sentences. Tells the user what the form does and how long it takes. Never includes instructions ("Please fill in…") — that's infantilizing.

Section marker

<div class="section-marker">
  § 01 &nbsp; <span class="section-name">Identity</span>
</div>
.section-marker {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  text-transform: uppercase;
  margin: 0 0 14px;
}
.section-name {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink-1);
  text-transform: none;
}

Field label

.field-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px;
}

Required indicator: asterisk * in --accent-success (green), NOT red. Again — green means "active state signal," not red-means-error. The asterisk is information, not a warning.

Field input

.field-input {
  width: 100%;
  height: 22px;
  padding: 0 0 2px;
  border: none;
  border-bottom: 0.5px solid var(--rule);
  background: transparent;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink-1);
  transition: border-color 0.15s, border-width 0.15s;
}
.field-input:focus {
  outline: none;
  border-bottom: 1px solid var(--accent-success);
}
.field-input::placeholder {
  color: var(--ink-4);
  font-size: 13px;
}

Textareas: same underline style, min-height: 60px, resize vertical only.

Pulldowns / selects: same visual treatment as inputs, with a custom chevron icon on the right. No native <select> arrow.

Primary button (Submit)

.btn-primary {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 8px 18px;
  background: var(--panel-dark);
  color: white;
  border: none;
  border-radius: 2px;
  cursor: pointer;
  transition: background 0.15s;
}
.btn-primary:hover {
  background: #253528;  /* lighter moss */
}
.btn-primary:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

Trailing arrow on submit buttons. "SUBMIT →" — subtle forward-motion cue.

Secondary / ghost button (Cancel)

.btn-ghost {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: transparent;
  color: var(--ink-3);
  border: none;
  padding: 6px 12px;
  cursor: pointer;
  transition: color 0.15s;
}
.btn-ghost:hover {
  color: var(--ink-1);
}

Every form has a footer separated by 0.5px solid var(--rule-faint). Left side: progress indicator in mono. Right side: Cancel + Submit.

3 OF 12 REQUIRED  ·  DRAFT      [CANCEL]  [SUBMIT →]

Success pill (Submitted page only)

.pill-success {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 10px;
  background: var(--accent-success-bg);
  color: #009B40;  /* darker green for AA contrast */
  border-radius: 999px;
}
.pill-success::before {
  content: "✓";
  font-weight: 500;
}

Route-specific notes

/ (Landing)

  • Centered, generous whitespace, no form chrome
  • Small INTERNAL PORTAL metadata line (mono, 10px) above the title
  • Large display title: Forms. (32px, Fraunces, 500)
  • One-line description: "Submit requests to the right team. Tickets created automatically, a copy goes to your inbox."
  • Primary action button: SIGN IN WITH OKTA → in --panel-dark
  • Footer line in mono tiny type: Need access? Email gpus-it@greenpeace.org

/forms (FormPicker)

  • Header: PICK A FORM (mono metadata label) + count 12 AVAILABLE in a second mono line
  • Forms grouped by category, each category gets a small mono header
  • Each form renders as a card on warm surface with hover border in --panel-dark
  • Form title uses display serif; description uses body sans at 0.92rem

/forms/:slug (FormFill)

  • Long-form vs short-form fork per the spec above
  • Lightweight client-side validation (required fields only) — backend is the real validator

/forms/:slug/submitted (Submitted)

  • Greenpeace green comes out here. This is the page where success lives.
  • Large ✓ SUBMITTED pill at top, green
  • Title: the form's title (so user knows what was submitted)
  • Card below with key-value pairs: submission ID, submitted at, HappyFox ticket, email status
  • SUBMIT ANOTHER button in --panel-dark
  • Minimal. White background, 0.5px bottom border.
  • Left: Forms · Greenpeace USA in display serif
  • Right (when authed): user email in mono + small admin pill if applicable + SIGN OUT ghost button

Accessibility

  • All interactive elements have :focus-visible states with a 2px outline in --accent-success
  • Minimum font size anywhere: 9px (monospace labels only — everything else is ≥11px)
  • Color contrast verified at AA minimum:
  • --ink-1 on --surface-warm: 16.2:1 ✓
  • --ink-3 on --surface-warm: 4.8:1 ✓
  • White on --panel-dark: 14.1:1 ✓
  • #009B40 on --accent-success-bg: 4.6:1 ✓
  • Form validation errors surface both in color (red) AND via text (e.g., "Required field") — never color alone
  • prefers-reduced-motion: all transitions collapse to 0.01ms

What this spec does NOT cover

  • Admin UI for editing forms — Phase 2.2. Forms are authored via YAML in the repo for Phase 2.
  • Submitter history / past submissions — intentionally out of scope per workflow ("fire and forget").
  • Mobile-specific layouts — the portal is internal staff use, assumed to be desktop-primary. The layout reflows reasonably at small widths but isn't optimized for touch.
  • Dark mode — not needed. The warm cream surface is deliberately the only mode.

Change log

Version Date Author Change
v1.0 2026-04-24 R. Chhetry Initial design spec — A+B refined direction with charcoal-moss CTA