Header / Navigation Redesign

Full header redesign: brand-navy bar, 4px purple accent strip, always-visible logo on mobile, high-contrast nav states, dark-mode as brand constant, and integrated login header. Replaces the current white bg-surface header that leaks the semantic surface color into what should be a brand-constant element.

Draft · 2026-03-30
Leonie Voss · Senior UX Designer
Header background
bg-surface (#fff)
→ brand-navy #012851
Top accent strip
None
→ 4px · brand-purple #B4B9FF
Active nav state
rgba purple pill (~1.08:1)
→ white + mint underline
Mobile logo
Hidden
→ Always visible, left side
Dark mode header
Flips to #1a1a1a
→ Stays brand-navy (constant)
Login page header
Hidden entirely
→ Brand header, logo-only
Language switcher (login)
Floating, no context
→ Integrated in login header right
Total header height
4px strip + 64px bar
= 68px total

What changes vs. current implementation

New / changed

  • Header bg-surface → fixed bg-brand-navy (#012851) — not theme-aware
  • 4px accent strip above header: background: #B4B9FF
  • Nav link colors on navy: inactive 55% white, hover 85% white, active 100% white
  • Active indicator: 2px bottom border in brand-mint (#A1DCD8) instead of rgba purple pill
  • Mobile: logo always visible left; hamburger icon white (was hidden or missing)
  • User avatar: mint background (#A1DCD8) with navy text (#012851)
  • Dark mode: dark:bg-surface override removed from header — stays navy
  • Login page: isAuthPage guard changed — shows logo-only header, not null
  • Language switcher on login: moved into header right slot
  • Mobile drawer: opens below navy header, white background, navy text links, mint active indicator

Kept unchanged

  • AppNav component structure — only CSS changes
  • Sticky header behavior (sticky top-0 z-50)
  • Max-width container and horizontal padding
  • NotificationBell, ThemeToggle, LanguageSwitcher components — only icon color changes
  • UserMenu component — only avatar color changes
  • Mobile drawer open/close logic
  • Admin nav link conditional visibility
  • isAuthPage derived value — still used, just different output
1 Current state — problems annotated
Desktop ≥768px 6 issues
/dokumente
Dokumente Personen Korrespondenz
DE
LV
Issues — desktop
  1. Background is bg-surface (white) — not brand. Every other archival app in this family uses a dark branded header.
  2. No 4px accent strip at top — missing the canonical brand-purple cap.
  3. Active link "Dokumente" uses rgba(180,185,255,0.15) on white = contrast ~1.08:1. Completely invisible. WCAG AA minimum is 3:1 for UI components.
  4. Logo is navy-on-white — works in light mode but will disappear in dark mode if header ever inherits #1a1a1a.
  5. Dark mode: header flips to near-black (#1a1a1a) — breaks brand consistency. Header should be a brand constant, not a semantic surface.
  6. User avatar: dark navy circle blends with any dark-mode context and provides no semantic meaning via color.
Mobile 375px logo missing
09:41
LV
Mobile issues
  • Logo hidden on mobile — brand identity completely lost
  • Hamburger icon is dark on white — fine in light mode, breaks in dark
  • Header still white — same surface-color problem as desktop
2 Proposed redesign — Desktop
Light mode ≥768px Proposed
/korrespondenz
Dokumente Personen Korrespondenz
DE
LV
4px accent strip · background: #B4B9FF
64px nav bar · background: #012851
Total: 68px
Active link: "Korrespondenz" — white text + 2px bottom border in #A1DCD8. Divider between logo and nav: rgba(255,255,255,0.15). Avatar: mint bg + navy text.
Dark mode ≥768px Same navy — brand constant
/korrespondenz
Dokumente Personen Korrespondenz
DE
LV
Dark mode rule: The header is a brand element, not a semantic surface. It does NOT respond to the dark: variant. Page content behind it switches; the header stays #012851.
  • Remove dark:bg-surface from header element
  • Apply bg-brand-navy as a non-dark-variant class
Element color tokens
brand-navy
#012851
Header bg
brand-purple
#B4B9FF
Accent strip
brand-mint
#A1DCD8
Active underline · avatar bg
white
#ffffff
Active link text · logo
white/55
rgba(255,255,255,.55)
Inactive nav links
white/85
rgba(255,255,255,.85)
Hover nav links
3 Nav link states
Inactive
Personen
color: rgba(255,255,255,.55)
4.9:1 ✓ AA
Intentionally muted — communicates "not here yet" without removing affordance.
Hover
Personen
color: rgba(255,255,255,.85)
7.8:1 ✓ AA
Smooth brightness step on hover. Transition: color 150ms ease.
Active (current page)
Korrespondenz
color: #ffffff
border-bottom: 2px solid #A1DCD8
21:1 ✓ AAA
Mint underline is the active indicator — not a background pill. Clear, low-weight, distinct from hover.
Focus (keyboard)
Personen
outline: 2px solid #A1DCD8
outline-offset: 3px
border-radius: 2px
3.4:1 ✓ AA
Mint outline on navy — meets WCAG 3:1 focus indicator requirement. Never suppress outline.
Active state contrast — before vs. after
Before Fails WCAG
Dokumente
Navy text (#012851) on rgba(180,185,255,0.15) on white.
Effective background: approx. #F4F4FF.
~1.08:1 ✗ Fail
The active pill is invisible. Users can't tell which page they're on.
After Passes WCAG AAA
Dokumente
White text (#ffffff) on navy (#012851).
Mint underline: #A1DCD8 on navy = 3.1:1 for the indicator itself.
21:1 ✓ AAA (text)
Unambiguous. The underline echoes brand-mint used elsewhere as an accent.
4 Mobile header + nav drawer
Current No logo
09:41
LV
Problem: No logo. The user has zero brand context. On first load, there is no visual cue that this is Familienarchiv. The hamburger icon color (dark navy) will also break in dark mode.
Proposed header Logo visible
09:41
LV
Logo always visible left. Avatar + hamburger right. Accent strip is 3px on mobile (saves 1px). Background is brand-navy — no theme variation.
Nav drawer Open state
09:41
LV
Sprache
DE
EN
ES
Drawer uses white background with navy text — intentional reversal of the dark header. Active page: mint left border + sand background. Language switcher lives in drawer on mobile (not floating).
5 Login page — branded header
Current — header hidden No brand context
/login
DE EN ES
Problems: Header is hidden entirely on auth pages. Language switcher floats top-right with no visual anchor — it's a ghost. Users arrive with zero brand context. The page could be any app.
Proposed — logo-only header Branded
/login
Accent strip + navy header appears on login. No nav links (user is not authenticated). Language switcher lives in header right slot — same position as desktop, consistent muscle memory. The brand is present from the first moment the user sees the app.
Implementation change: In +layout.svelte, the {#if !isAuthPage} guard currently hides the entire header. Replace with a conditional that renders a login variant of the header (logo + lang switcher, no nav links) when isAuthPage is true. Move the LanguageSwitcher import into the header for the auth variant. Remove the floating LanguageSwitcher from /login/+page.svelte.
6 Right utility area — element by element
DE EN ES
🔔
LV
Language switcher
Active lang: color: #ffffff
Inactive lang: color: rgba(255,255,255,.5)
Separator from rest: border-right: 1px solid rgba(255,255,255,.15)
On login: visible in header right slot
Theme toggle
Icon: white at opacity: 0.7
Hover: opacity: 1.0
Background: rgba(255,255,255,.1)
No change to toggle logic — icon color only
Notification bell
Icon: white at opacity: 0.75
Badge: stays bg-red-500 (#EF4444)
Badge border: border: 1.5px solid #012851 (halos on navy)
No component logic changes
User avatar
Background: #A1DCD8 (brand-mint)
Text: #012851 (brand-navy)
Contrast: 4.8:1 ✓ AA
Replaces navy bg (dark-on-dark in dark mode)

Implementation notes

CSS / Tailwind changes

  • Header: replace bg-surface with bg-[#012851] (or add a bg-brand-navy utility to layout.css)
  • Remove border-b border-line-2 from header — the accent strip replaces the visual separator
  • Add a <div class="h-1 bg-[#B4B9FF]"> before the nav bar in +layout.svelte
  • Nav links: replace text-ink + bg-nav-active with opacity-based white utilities: text-white/55 inactive, hover:text-white/85, text-white border-b-2 border-[#A1DCD8] active
  • User avatar: swap bg-brand-navy text-whitebg-[#A1DCD8] text-[#012851]
  • Notification badge: add border-2 border-[#012851] to badge element
  • Dark mode: on the <header> element, ensure there is NO dark: variant overriding the background

Component changes

  • +layout.svelte: split the {#if !isAuthPage} guard into two branches — full header (authed) vs. login header (logo + lang only)
  • AppNav.svelte: ensure logo is always rendered, not hidden on mobile via hidden sm:flex or similar
  • AppNav.svelte: hamburger button — icon color from dark to text-white/85
  • AppNav.svelte: active link class — remove bg-nav-active, add bottom border in mint
  • UserMenu.svelte: avatar background and text color
  • ThemeToggle.svelte: icon fill/stroke → text-white/70
  • NotificationBell.svelte: icon color → text-white/75
  • /login/+page.svelte: remove standalone <LanguageSwitcher> — it moves to the layout header

CSS variable candidates

  • Consider adding to layout.css:
    --header-bg: #012851;
    --header-accent: #B4B9FF;
    --header-nav-active: #A1DCD8;
  • These are intentionally NOT in the dark-mode @media (prefers-color-scheme: dark) block — they are brand constants
  • If Tailwind 4 theme is configured, add:
    brand-navy: #012851
    brand-purple: #B4B9FF
    brand-mint: #A1DCD8
    to the @theme block in layout.css
  • No backend changes required
  • No i18n key changes required
  • No new routes required