Register Page · Final Design Spec

Centered-card variant (Variant A). Friendly, inviting registration form for Familienarchiv — a collaborative archive of family letters from the 19th and 20th centuries. Designed for a dual audience (60+ and 25–42), with large 17px serif inputs, du-Anrede (informal you), German-first with DE/EN/ES toggle, live password-match feedback, mention-notification opt-in, and a post-submit success panel.

Final · Ready for implementation
Variant
A — Centered card · 640px white card on sand canvas
Fields
Vorname · Nachname · E-Mail · Passwort · Passwort bestätigen · Benachrichtigungen
Languages
DE (default) / EN / ES — header toggle; swaps all labels, hints, and validation messages live
Audience
60+ primary (48px touch targets, 17px serif, calm layout) · 25–42 secondary
📐 Mockup scale notice — all font-size, height, and padding values in the mockup CSS are scaled to ~55% of actual implementation values. Do not copy sizes from mockup CSS. Use the ⚙ Implementation Reference tables after each section.
1 Full page — desktop (1280px)
Default — form at rest, German language
localhost:3000/register
Familienarchiv
Ein Familienprojekt
Schön, dass du da bist.
Bereits 1.500 Briefe aus vielen Jahrzehnten sind im Archiv. Mit deinem Konto hilfst du mit, sie zu lesen, zu ordnen und für die nächsten Generationen zu bewahren.
Über dich
Vorname
Nachname
Konto
E-Mail-Adresse
Passwort
Mindestens 8 Zeichen.
Passwort bestätigen
Benachrichtigungen
Benachrichtige mich,
wenn jemand mich in einem Kommentar erwähnt oder mir auf einen Kommentar antwortet.
Implementation Reference — Page structureroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Page wrapperflex min-h-screen flex-col bg-canvasSame pattern as login page
Main centering wrapperflex flex-1 justify-center items-start px-8 pt-1664px top paddingItems start (not center) so tall forms don't overflow
Content columnw-full max-w-[640px]640px maxFixed max-width card column
Form cardbg-surface border border-line rounded-sm shadow-sm p-1040px paddingrounded-sm = 2px; shadow-sm = 0 1px 2px rgb(0 0 0/.05)
2 Header — anatomy & language toggle states
DE active (default)
localhost:3000/register
Familienarchiv
Active language: mint underline + white text. Inactive: 55% opacity.
EN active
localhost:3000/register?lang=en
Familienarchiv
Toggle switches all copy live (no page reload). All labels, hints, and validation messages switch language.
ES active
localhost:3000/register?lang=es
Familienarchiv
Implementation Reference — Headerroutes/AuthHeader.svelte (extend) or new RegisterHeader.svelte
ElementTailwind classesReal pxNotes
Header wrappersticky top-0 z-50 bg-[#012851]Same as authenticated header but no nav links (unauthenticated page)
Mint accent stripeh-1 bg-accent4px--c-accent: #a1dcd8. Always at the very top of the header.
Nav barh-16 flex items-center px-864px tall, 32px side paddingMax-width container inside: max-w-screen-xl mx-auto w-full
Wordmarkfont-sans text-xl font-bold tracking-[.15em] text-white uppercase20pxNot a link on the register page (user is not yet logged in)
Lang toggle containerml-auto flex items-center gap-14px gap
Lang button — inactivefont-sans text-xs font-bold tracking-[.12em] uppercase text-white/55 px-2.5 py-1.5 border-b-2 border-transparent transition-colors hover:text-white12px, padding 6px 10pxTouch target: 12+6+6 = 24px tall, acceptable for a header toggle since it's not a primary action
Lang button — activetext-white border-b-2 border-accentMint underline border
3 Above-card — eyebrow, headline, subtext
German (DE)
Ein Familienprojekt
Schön, dass du da bist.
Bereits 1.500 Briefe aus vielen Jahrzehnten sind im Archiv. Mit deinem Konto hilfst du mit, sie zu lesen, zu ordnen und für die nächsten Generationen zu bewahren.
English (EN)
A family project
Glad to have you here.
Already 1,500 letters from many decades are in the archive. Your account helps read them, organise them, and keep them safe for the next generations.
Spanish (ES)
Un proyecto familiar
Qué bien tenerte aquí.
Ya hay 1.500 cartas de muchas décadas en el archivo. Con tu cuenta ayudas a leerlas, organizarlas y conservarlas para las próximas generaciones.
Implementation Reference — Above-cardroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Above-card wrappertext-center mb-936px bottom margin
Eyebrow labelinline-block font-sans text-[11px] font-bold tracking-[.22em] uppercase text-ink-2 border-t border-b border-[#d4d2c5] px-3.5 py-1.5 mb-[18px]11px / padding 6px 14pxi18n key: m.register_eyebrow()
Headlinefont-serif font-normal text-[46px] leading-[1.12] tracking-[-0.005em] text-ink mb-446pxi18n key: m.register_headline(). Tinos (serif), not bold — weight 400.
Subtextfont-serif text-lg leading-relaxed text-ink-2 max-w-[540px] mx-auto18px, max-width 540pxi18n key: m.register_sub(). text-wrap: pretty via inline style for better line breaks.
4 Form section — Über dich (name fields)
Empty
Über dich
Vorname
Nachname
Focused — Vorname
Über dich
Vorname
Nachname
Focus: navy border + soft navy glow ring.
Filled
Über dich
Vorname
Nachname
Error — submitted empty
Über dich
Vorname
Nachname
Red border only — no text error for empty name fields (border color communicates invalid state on submit).
Implementation Reference — Name fieldsroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Section caption wrapperflex items-center gap-3 mb-1Gap 12px between text and rule line
Section caption textfont-sans text-[11px] font-bold tracking-[.18em] uppercase text-ink whitespace-nowrap11pxi18n key: m.register_section_about()
Section caption ruleflex-1 h-px bg-line1px
2-column name gridgrid grid-cols-2 gap-4 mt-416px gap, 16px top marginOn mobile (<480px): grid-cols-1
Field wrapperflex flex-col
Field labelblock font-sans text-xs font-bold tracking-[.1em] uppercase text-ink-2 mb-212px, 8px bottom margini18n keys: m.register_first_name(), m.register_last_name()
Text input — defaultw-full border border-line bg-surface font-serif text-[17px] text-ink placeholder:text-ink-3 px-4 py-[14px] rounded-none outline-none focus-visible:ring-2 focus-visible:ring-focus-ring/15 focus-visible:border-ink transition-colors17px font / 48px tall (14+14+17+1px borders)No border-radius (Tailwind's rounded-none keeps it at 2px via border-radius:2px from global reset — or use rounded-sm)
Text input — invalid+ border-danger focus-visible:ring-danger/20Applied when submitted && !field.trim()
Placeholdersplaceholder:text-ink-3DE: "z.B. Frieda" / "z.B. Lehmann" · EN: "e.g. Frieda" / "e.g. Lehmann"
5 Form section — Konto (e-mail & passwords)
Default — empty
Konto
E-Mail-Adresse
Passwort
Mindestens 8 Zeichen.
Passwort bestätigen
Password valid + passwords match
Konto
E-Mail-Adresse
Passwort
Mindestens 8 Zeichen. ✓
Passwort bestätigen
Passwörter stimmen überein.
Green dot + green text when passwords match. Show button label changes to "Verbergen".
Password mismatch
Konto
E-Mail-Adresse
Passwort
Mindestens 8 Zeichen. ✓
Passwort bestätigen
Die beiden Passwörter stimmen noch nicht überein.
Red border on confirm field + red error text. No dot icon for errors.
Implementation Reference — Konto fieldsroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Email inputw-full border border-line bg-surface font-serif text-[17px] text-ink placeholder:text-ink-3 px-4 py-[14px] rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-focus-ring/15 focus-visible:border-ink transition-colors17px font / 48px talltype="email", autocomplete="email"
Password input wrapperrelativeContains input + show/hide button
Password inputSame as email input + pr-[92px]92px right padding to clear show/hide btntype="password" toggles to type="text" on show
Show/hide buttonabsolute right-0 inset-y-0 px-3.5 font-sans text-[11px] font-bold tracking-[.1em] uppercase text-ink-2 border-l border-line hover:text-ink transition-colors11px fonttype="button" (prevent form submit). Label: m.password_show() / m.password_hide()
Hint text — neutralfont-sans text-[13px] text-ink-3 mt-1.513px, 6px top margini18n key: m.register_pw_hint() ("Mindestens 8 Zeichen.")
Hint text — success (match)font-sans text-[13px] text-[#0a6b56] mt-1.5 flex items-center gap-1.5Shown when pw.length > 0 && pw === pw2. Dot: w-2 h-2 rounded-full bg-turquoise inline-block
Hint text — error (mismatch)font-sans text-[13px] text-danger mt-1.5Shown when pw2.length > 0 && pw !== pw2. i18n key: m.register_pw_match_no()
Validation logicpwValid: length ≥ 8. pwMatch: pw === pw2 && pw.length > 0. pwMismatch: pw2.length > 0 && pw !== pw2. Errors only shown after first submit attempt or onBlur.
6 Notification preference — CheckOption card
Unchecked
Benachrichtigungen
Benachrichtige mich,
wenn jemand mich in einem Kommentar erwähnt oder mir auf einen Kommentar antwortet.
Border: sand (#e4e2d7). Background: white. Checkbox: gray border, white fill.
Checked (default state)
Benachrichtigungen
Benachrichtige mich,
wenn jemand mich in einem Kommentar erwähnt oder mir auf einen Kommentar antwortet.
Default state: checked (opt-in by default). Border: navy. Background: mint tint (rgba(161,220,216,.18)).
Implementation Reference — CheckOption cardroutes/register/+page.svelte
ElementTailwind classes / valuesReal pxNotes
Card wrapper (label)<label> with flex gap-3.5 p-3.5 border rounded-sm cursor-pointer transition-colors items-start14px gap, 14px 16px paddingThe entire card is a <label> for large click target. Wrap real <input type="checkbox"> visually hidden inside.
Card — uncheckedborder-line bg-surface
Card — checkedborder-ink bg-[rgba(161,220,216,0.18)]Mint tint bg. No Tailwind utility exists — use inline style or arbitrary value.
Real checkbox inputsr-only (visually hidden, accessible)Bind checked state to this. Default: true.
Custom checkbox boxflex-shrink-0 w-[22px] h-[22px] rounded-sm border-2 flex items-center justify-center mt-px transition-all22×22pxMin touch target met by the card itself.
Checkbox — uncheckedborder-gray-400 bg-surface
Checkbox — checkedborder-ink bg-inkWhite checkmark SVG inside.
Checkmark SVG14×14px, path: M3 8.5l3.2 3.2L13 5, stroke white 2.4, round caps14px iconOnly rendered when checked.
Option titlefont-sans text-sm font-semibold text-ink14pxi18n key: m.register_notify_label()
Option descriptionfont-serif text-[15px] leading-normal text-ink-2 mt-0.515pxi18n key: m.register_notify_desc()
7 Submit button & footer links
Default
Submit — hover
Bg darkens: #012851 → #01386f. No scale transform.
Loading (after submit)
Arrow icon replaced by spinner. Button opacity 0.85. cursor: wait. 850ms simulated delay then success.
Implementation Reference — Submit & footerroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Submit buttonw-full bg-primary text-primary-fg font-sans text-[13px] font-bold tracking-[.12em] uppercase py-4 px-6 rounded-sm flex items-center justify-center gap-2.5 transition-colors hover:bg-[#01386f] mt-813px font / 16px top+bottom padding / ~53px tallArrow icon: 16×16px SVG. Loading state: add opacity-85 cursor-wait, swap icon for spinner.
Loading spinnerw-4 h-4 rounded-full border-2 border-white/30 border-t-white animate-spin16pxReplaces arrow icon during loading state
Footer wrappermt-6 flex flex-col gap-3.5 text-center24px top margin, 14px gap
Privacy textfont-serif text-sm text-ink-3 leading-relaxed14pxLinks within: text-ink underline decoration-accent underline-offset-[3px] decoration-2
Sign-in promptfont-sans text-[13px] text-ink-213pxi18n: m.register_have_account() + m.register_sign_in()
Sign-in linkfont-sans text-xs font-bold tracking-[.1em] uppercase text-ink underline decoration-accent underline-offset-[4px] decoration-212pxhref="/login"
8 Success panel — post-submit state
Success — German
localhost:3000/register
Familienarchiv
Willkommen im Familienarchiv.
Wir haben dir eine E-Mail an frieda@beispiel.de geschickt, um deine Adresse zu bestätigen. Anschließend kannst du dich anmelden.
Success — English
localhost:3000/register?lang=en
Familienarchiv
Welcome to Familienarchiv.
We sent an email to frieda@example.com to confirm your address. After that, you can sign in.
Implementation Reference — Success panelroutes/register/+page.svelte
ElementTailwind classesReal pxNotes
Success wrappertext-center py-28px top/bottom paddingReplaces the entire <form> block. Same card container as the form.
Checkmark circlew-[72px] h-[72px] rounded-full bg-[rgba(161,220,216,0.35)] inline-flex items-center justify-center mb-672pxCheckmark SVG: 34×34px, stroke #012851 2.2px.
Success headlinefont-serif font-normal text-[30px] leading-tight text-ink mb-3.530pxi18n key: m.register_success_h()
Success bodyfont-serif text-[17px] leading-[1.55] text-ink-2 max-w-[420px] mx-auto mb-717px, max 420pxi18n key: m.register_success_b(). Replace {email} token with entered email.
CTA buttonSame as submit button — full-width navyhref="/login". i18n: m.register_success_action()
Resend linkfont-sans text-xs font-bold tracking-[.1em] uppercase text-ink-2 px-2.5 py-2.5 underline decoration-accent underline-offset-[4px] decoration-212pxi18n: m.register_success_resend(). Calls /api/auth/resend-verification. Container: max-w-[320px] mx-auto flex flex-col gap-2.5
9 Mobile layout — 320px
320px — form at rest
9:41
Familienarchiv
Ein Familienprojekt
Schön, dass du
da bist.
Bereits 1.500 Briefe sind im Archiv. Mit deinem Konto hilfst du mit, sie zu bewahren.
Über dich
Vorname
Nachname
Konto
E-Mail
Passwort
Passwort best.
Name grid collapses to single column. Subtext shortened. Card padding reduced. All inputs remain 17px on real device (browser zooms accordingly).
320px — filled, ready to submit
9:41
Familienarchiv
Schön, dass du
da bist.
Über dich
Vorname
Nachname
Konto
E-Mail
Passwort
✓ 8 Zeichen
Best.
Stimmen überein.
320px — success panel
9:41
Familienarchiv
Willkommen im Familienarchiv.
Wir haben dir eine E-Mail an frieda@b.de geschickt, um deine Adresse zu bestätigen.
Implementation Reference — Mobile breakpointsroutes/register/+page.svelte
BreakpointChangeTailwindNotes
Default (mobile-first, <480px)Single-column name gridgrid grid-cols-1 gap-4Start mobile-first; add sm:grid-cols-2 at 480px+
sm: 480px+2-column name gridsm:grid-cols-2Custom breakpoint or use min-[480px]:grid-cols-2
Default (mobile-first)Card padding 24pxp-6Reduced from desktop 40px
sm: 640px+Card padding 40pxsm:p-10Full desktop padding
Default (mobile-first)Main padding 32px toppt-8Reduced from desktop 64px
sm: 640px+Main padding 64px topsm:pt-16
All breakpointsInput font size 17pxtext-[17px]Do NOT reduce on mobile — large text is critical for the 60+ audience. The browser will zoom the viewport to show text clearly.
All breakpointsTouch targets ≥ 44pxButton: py-4 = 16px × 2 + 13px font = 45px ✓CheckOption card: entire card is the touch target (~52px+ tall) ✓
10 Implementation notes — i18n, accessibility, backend
i18n keys (Paraglide)
New keys — messages/de.json + en.json + es.json
KeyDE
register_eyebrowEin Familienprojekt
register_headlineSchön, dass du da bist.
register_subBereits 1.500 Briefe aus vielen Jahrzehnten…
register_section_aboutÜber dich
register_section_accountKonto
register_section_prefsBenachrichtigungen
register_first_nameVorname
register_last_nameNachname
register_emailE-Mail-Adresse
register_passwordPasswort
register_password_confirmPasswort bestätigen
register_pw_hintMindestens 8 Zeichen.
register_pw_match_okPasswörter stimmen überein.
register_pw_match_noDie beiden Passwörter stimmen noch nicht überein.
register_notify_labelBenachrichtige mich,
register_notify_descwenn jemand mich in einem Kommentar erwähnt…
register_submitKonto erstellen
register_submit_loadingWird erstellt…
register_have_accountDu hast bereits ein Konto?
register_sign_inAnmelden
register_success_hWillkommen im Familienarchiv.
register_success_bWir haben dir eine E-Mail an {email} geschickt…
register_success_actionZur Anmeldung
register_success_resendE-Mail erneut senden
register_privacyMit dem Erstellen eines Kontos stimmst du der
register_privacy_linkDatenschutzerklärung
register_terms_linkNutzungsbedingungen
password_showAnzeigen
password_hideVerbergen
page_title_registerRegistrieren — Familienarchiv
Accessibility requirements
WCAG 2.2 checklist
CriterionImplementationLevel
1.4.3 Contrast (text)Navy #012851 on white: 14.5:1 ✓. Gray #4b5563 on white: 7.6:1 ✓. Gray #6b7280 on white: 5.9:1 ✓ (AA only — use for large text / hints)AA
2.4.2 Page title<svelte:head><title>{m.page_title_register()}</title>A
1.3.1 Form labelsEvery input has <label for="...">. No placeholder-as-label.A
2.4.3 Focus orderTab order: DE/EN/ES → Vorname → Nachname → Email → Password → Confirm → Checkbox → SubmitA
2.4.7 Focus visiblefocus-visible:ring-2 focus-visible:ring-focus-ring/15 on all inputs. focus-visible:ring-2 focus-visible:ring-white/80 on submit button.AA
2.5.3 Touch target (2.2)All buttons ≥ 44px tall. CheckOption card ≥ 44px. Show/hide button: 44px tall (same as input height).AA
4.1.3 Status messagesPassword match/mismatch feedback announced via aria-live="polite" region.AA
1.4.4 Resize textAll sizes in rem/px that scale with browser zoom. 17px input font = 1.0625rem.AA
Error identificationaria-invalid="true" on invalid inputs. aria-describedby linking to error message element.A
Backend wiring
Route + API integration
ConcernDetails
Routefrontend/src/routes/register/+page.svelte + +page.server.ts
Form actionPOST ?/register+page.server.ts action
API endpointPOST /api/auth/register — create new AppUser (first name, last name, email, password, notifyOnMention)
On successBackend sends verification email. Frontend shows success panel with entered email.
Resend endpointPOST /api/auth/resend-verification — body: { email }
Client-side validationRun before server submit: required fields, email format, password ≥ 8 chars, password match. Show errors inline on first submit attempt.
Server-side errorsMap backend error codes via getErrorMessage(code): EMAIL_ALREADY_EXISTS → Paraglide key. Never display raw backend messages.
notifyOnMention fieldCheckOption checked state maps to notifyOnMention: boolean in request body. Default: true.
Language preferenceThe active language is a client-side Paraglide state only (page.svelte $state). Not persisted until the user creates an account; can be added to the registration payload as preferredLanguage: 'de' | 'en' | 'es' if the backend supports it.
Auth headerRegister page is unauthenticated. Use a minimal header (AuthHeader without nav links) or a dedicated RegisterHeader.
Implementation sequence:
  • 1. Add POST /api/auth/register to Spring Boot backend (new controller + service method)
  • 2. Add POST /api/auth/resend-verification
  • 3. Regenerate TypeScript API types (npm run generate:api)
  • 4. Implement frontend/src/routes/register/+page.svelte
  • 5. Add i18n keys to all three message files
  • 6. Add route to navigation (unauthenticated footer or login page link)