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.mdv1.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 schema — fields 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.5rembottom margin after each section's fields - Footer border-top:
0.5px solid var(--rule-faint)with1rempadding 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¶
.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);
}
Footer strip¶
Every form has a footer separated by 0.5px solid var(--rule-faint). Left side: progress indicator in mono. Right side: 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 PORTALmetadata 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) + count12 AVAILABLEin 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
✓ SUBMITTEDpill 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 ANOTHERbutton in--panel-dark
Header¶
- Minimal. White background, 0.5px bottom border.
- Left:
Forms · Greenpeace USAin display serif - Right (when authed): user email in mono + small admin pill if applicable + SIGN OUT ghost button
Accessibility¶
- All interactive elements have
:focus-visiblestates with a2pxoutline in--accent-success - Minimum font size anywhere: 9px (monospace labels only — everything else is ≥11px)
- Color contrast verified at AA minimum:
--ink-1on--surface-warm: 16.2:1 ✓--ink-3on--surface-warm: 4.8:1 ✓- White on
--panel-dark: 14.1:1 ✓ #009B40on--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 to0.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 |