Sort Integration — 4 Exploration Variants

Four distinct approaches for adding sort controls to the document search. Issue #180 (Problem 1). Each variant has a different placement philosophy — choose based on the user model you want to reinforce. Not a final spec; pick one and lock it down.

Exploration · Pick One
Issue
#180 — Sort & Search UX
Sort options (6)
Datum · Titel · Absender · Empfänger · Tag · Hochgeladen
Direction
Aufsteigend ↑ / Absteigend ↓ per option
New URL params
?sort=date&dir=desc
📐 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.
A Inline sort in search bar row Sort dropdown sits directly in row 1 — always visible, no extra space

Pros

  • Always visible — zero discovery friction
  • Sort and search feel like one cohesive control surface
  • Familiar pattern (GitHub Issues, Notion tables)
  • Active state is immediately legible without opening anything

Cons

  • Row 1 gets crowded at 320px — needs a wrapping strategy
  • Sort button competes visually with Filter button
  • Dropdown overlaps results on short viewports

Best when

  • Sort is a primary, frequently changed action
  • Users already understand the search bar as a control hub
  • You want to minimize total UI surface area
Desktop — dropdown open
localhost:3000/?q=brief&sort=date&dir=desc
Dokumente Personen
brief
Sortieren: Datum ↓
Datum
Titel
Absender
Empfänger
Tag
Hochgeladen
Filter
12 Dokumente gefunden
PDF
Brief an Tante Klara, Weihnachten 1954
15. Dezember 1954 · Ernst Raddatz → Klara Meier · Briefe
PDF
Briefwechsel Sommer 1961
3. Juli 1961 · Hildegard Raddatz → Stadtamt · Verwaltung
Sort button sits between search input and Filter button. Dropdown shows all 6 options with per-option ↑/↓ direction toggle. Active option highlighted in navy.
Mobile 320px — collapsed (no open dropdown)
9:41
brief
Datum ↓
⊞ Filter
12 Dokumente gefunden
PDF
PDF
On mobile, sort wraps to a second row inside the search card. Both sort and filter become equal-width buttons — same touch target, consistent layout.
Implementation Reference — Variant A: Inline Sort Real values · mockup above is ~55% scale · do not copy mockup CSS
ElementTailwind classesReal sizeNotes
Sort trigger button h-10 flex items-center gap-2 border border-line px-3 text-sm font-bold text-ink-2 bg-muted hover:bg-surface transition whitespace-nowrap rounded-sm h 40px, px 12px Active state: border-brand-navy text-brand-navy bg-surface
Sort prefix label text-xs font-normal text-ink-3 mr-1 12px / 400 "Sortieren:" prefix — hide on mobile with hidden sm:inline
Sort dropdown container absolute top-full left-0 z-50 min-w-[180px] bg-surface border border-brand-navy border-t-0 shadow-lg rounded-b-sm min-w 180px Opens below button; close on outside click via onpointerdown listener
Dropdown option row flex items-center px-3 py-2.5 gap-3 border-b border-line cursor-pointer hover:bg-muted last:border-b-0 h ~40px, py 10px Touch target ≥ 44px — most commonly undersized element
Dropdown option label flex-1 text-sm font-semibold text-ink 14px / 600 Active row: text-brand-navy + row background bg-blue-50
Direction toggle (↑/↓) flex gap-1 — each button: text-xs font-bold px-1.5 py-0.5 rounded-sm border border-transparent text-ink-3 24px touch area min Active direction: bg-brand-navy text-white border-brand-navy
Mobile: sort + filter row flex gap-2 mt-2 — each button adds flex-1 justify-center h 40px, flex-1 Wraps to second line inside .SCARD below sm. Both buttons equal width.
New URL params sort + dir Values: sort=date|title|sender|receiver|tag|uploaded, dir=asc|desc. Add to triggerSearch() in +page.svelte and to /api/documents/search query params.
B Sort pill strip below search card A horizontally scrollable row of pills between search and results

Pros

  • All 6 options visible at a glance — no dropdown needed
  • Active sort is unmistakable (navy pill, always in view)
  • Natural horizontal scroll on mobile — familiar to users
  • Search bar stays clean and single-purpose

Cons

  • Adds a full row of vertical space even when sort isn't used
  • 6 options may overflow without scroll cue on narrow screens
  • Seniors may not discover horizontal scroll on mobile

Best when

  • Sort is a secondary action but users switch it often
  • You want sort visible without modal/dropdown overhead
  • The team prefers a "tab strip" mental model over a select
Desktop — "Absender" active, ascending
localhost:3000/?q=brief&sort=sender&dir=asc
Dokumente Personen
brief
⊞ Filter
Sortieren:
Datum
Titel
Absender ↑
Empfänger
Tag
Hochgeladen
↑ A–Z
↓ Z–A
12 Dokumente, sortiert nach Absender ↑
PDF
Brief an Tante Klara, Weihnachten 1954
Ernst Raddatz · 15. Dez 1954 · Briefe
PDF
Urlaubspostkarte 1962
Hildegard Raddatz · 8. Aug 1962 · Karten
Pill strip lives between search card and results as its own card. Active pill is navy-filled. Direction buttons (↑/↓) are separate toggle at the right end. Result count confirms active sort.
Mobile 320px — horizontal scroll
9:41
brief
Datum ↓
Absender ↑
Empfänger
Tag…
12 Dokumente, Absender ↑
PDF
PDF
On mobile the pill row clips at screen edge — the half-visible last pill cues horizontal scroll. Direction toggle moves inside the active pill label (↑/↓ suffix). Min touch target: 44px height enforced in implementation.
Senior accessibility note: Horizontal scroll is invisible on desktop and may be missed by seniors on tablet. Consider adding a faint right-fade gradient mask (-webkit-mask-image: linear-gradient(to right, #000 80%, transparent)) to the pill container to signal overflow.
Implementation Reference — Variant B: Sort Pill Strip Real values · mockup above is ~55% scale
ElementTailwind classesReal sizeNotes
Pill strip container flex items-center gap-2 overflow-x-auto bg-surface border border-line rounded-sm px-4 py-3 shadow-sm scroll-smooth h ~48px, py 12px Add scrollbar-hide (Tailwind plugin) or ::-webkit-scrollbar { display: none }. Right-fade mask on mobile.
"Sortieren:" label text-xs font-bold uppercase tracking-widest text-ink-3 shrink-0 mr-1 12px / 700 Hide on mobile: hidden sm:block
Inactive pill h-8 shrink-0 flex items-center gap-1 px-3 rounded-full border border-line text-sm font-semibold text-ink-2 cursor-pointer hover:border-brand-navy hover:text-brand-navy transition whitespace-nowrap h 32px, px 12px Touch target: wrap in min-h-[44px] flex items-center on mobile. Most commonly undersized element.
Active pill Inactive classes + bg-brand-navy text-white border-brand-navy h 32px Shows direction suffix: "Absender ↑". Screen reader: aria-pressed="true"
Direction toggles (↑/↓) shrink-0 h-8 flex items-center gap-1 px-3 border border-line text-sm font-bold text-ink-2 rounded-sm cursor-pointer hover:border-brand-navy transition h 32px, px 12px Active: border-brand-navy text-brand-navy. aria-label="Aufsteigend" / "Absteigend"
Separator w-px h-5 bg-line shrink-0 mx-1 1px × 20px Divides pills from direction buttons. role="separator" aria-hidden="true"
Result count confirmation text-sm font-medium text-ink-2 mb-2 14px Text: "12 Dokumente, sortiert nach Absender ↑". Live region: aria-live="polite"
C Sort in the result-list header Sort lives above the document list, co-located with result count — zero search bar pollution

Pros

  • Search bar stays completely clean — single responsibility
  • Sort sits next to what it affects — contextually obvious
  • Result count + sort in one bar = efficient use of space
  • Pattern familiar from e-commerce (Amazon, Zalando)

Cons

  • Sort is below the fold on mobile — users may not scroll to find it
  • Only visible in search mode (disappears on dashboard)
  • Seniors may not understand why sort isn't near the search input

Best when

  • Sort is considered a result-manipulation tool, not a search param
  • The result list is long enough to justify a persistent header
  • You want a clean aesthetic in the search bar at all costs
Desktop — sort dropdown open
localhost:3000/?q=brief&sort=date&dir=desc
Dokumente Personen
brief
⊞ Filter
12 Dokumente gefunden
Sortieren: Datum ↓
Datum
Titel
Absender
Empfänger
Tag
Hochgeladen
PDF
Brief an Tante Klara, Weihnachten 1954
15. Dez 1954·Ernst Raddatz·Briefe
PDF
Briefwechsel Sommer 1961
3. Jul 1961·Hildegard Raddatz·Verwaltung
Result header is its own card above the document list. Left: result count. Right: sort dropdown trigger. Sort dropdown opens upward on mobile if needed. This variant only renders during search — not on dashboard.
Mobile 320px — result header sticky
9:41
brief
12 Dok.
Sort: Datum ↓
PDF
PDF
Result header is position: sticky; top: 0 on mobile so it stays in view while scrolling the document list. Sort dropdown opens upward (bottom: 100%) to avoid viewport overflow.
Implementation Reference — Variant C: Result-Header Sort Real values · mockup above is ~55% scale
ElementTailwind classesReal sizeNotes
Result header bar flex items-center gap-3 px-4 py-3 bg-surface border border-line rounded-sm shadow-sm sticky top-0 z-20 h ~48px, py 12px Sticky on mobile. Only rendered when !isDashboard — wrap in {#if !data.isDashboard} in +page.svelte
Result count text flex-1 text-sm font-medium text-ink-2 14px / 500 <strong class="text-brand-navy">12</strong> Dokumente gefunden. aria-live="polite"
Sort trigger (result header) h-9 flex items-center gap-2 border border-line px-3 text-sm font-bold text-ink-2 bg-surface rounded-sm hover:border-brand-navy transition whitespace-nowrap h 36px, px 12px Active: border-brand-navy text-brand-navy. Touch target min 44px: add min-h-[44px] on mobile.
Sort dropdown (result header) absolute bottom-full right-0 mb-1 z-50 min-w-[180px] bg-surface border border-brand-navy shadow-lg rounded-sm min-w 180px Opens upward (bottom-full) on mobile to stay in viewport. Same option rows as Variant A.
Dropdown option row flex items-center px-3 py-2.5 gap-3 border-b border-line last:border-b-0 cursor-pointer hover:bg-muted h ~40px Touch target ≥ 44px — most commonly undersized
Direction toggle Same as Variant A direction toggle Reuse same component
Svelte placement Add ResultHeader.svelte component. Render between SearchFilterBar and DocumentList in +page.svelte, only when !data.isDashboard.
D Sort inside the advanced filter panel Sort lives at the top of the collapsible "Filter" section — hidden by default

Pros

  • Zero additional chrome when filters are closed
  • Logical grouping: "filtering and sorting" as one concept
  • Reveals naturally when filter badge shows active sort
  • Works well if sort is rarely changed once set

Cons

  • Discoverability is low — users must click "Filter" to find sort
  • Seniors may not think to look inside "Filter" for sort
  • Active sort is invisible when filter panel is collapsed

Best when

  • Sort is rarely changed (users set it once and leave it)
  • You want to keep the primary UI completely minimal
  • The Filter button shows an active-state badge when sort is non-default
Desktop — advanced filter open, sort section visible
localhost:3000/?q=brief&sort=sender&dir=asc
Dokumente Personen
brief
⊞ Filter 1
Sortieren nach
Datum
Titel
Absender
Empfänger
Tag
Hochgeladen
↑ Aufsteigend
↓ Absteigend
Absender
Person wählen…
Empfänger
Person wählen…
Von
Datum…
Bis
Datum…
12 Dokumente, sortiert nach Absender ↑
PDF
Brief an Tante Klara, Weihnachten 1954
Ernst Raddatz·15. Dez 1954·Briefe
Sort sits at the top of the advanced filter panel — above Absender, Empfänger, dates. When sort is non-default, the Filter button shows a navy badge with the count of active modifiers (sort + any active filters).
Desktop — filter closed, active badge visible
localhost:3000/?q=brief&sort=sender&dir=asc
Dokumente Personen
brief
⊞ Filter (1)
12 Dokumente, sortiert nach Absender ↑
PDF
Brief an Tante Klara, Weihnachten 1954
Ernst Raddatz·15. Dez 1954·Briefe
PDF
Briefwechsel Sommer 1961
Hildegard Raddatz·3. Jul 1961·Verwaltung
Filter button shows "(1)" suffix when sort is non-default. The result count line confirms active sort. This is the only surface that signals sort is active when the panel is collapsed — the discoverability weakness of this variant.
Discoverability mitigation: If choosing Variant D, the Filter button label should read "Filter (1)" (or show a dot badge) whenever sort is non-default. Additionally, the result-count line should always name the active sort: "12 Dokumente, Absender ↑". These two surfaces together are the only visual hints that a sort is active when the panel is closed.
Implementation Reference — Variant D: Sort in Advanced Filter Panel Real values · mockup above is ~55% scale
ElementTailwind classesReal sizeNotes
Sort section in panel border-b border-line pb-5 mb-5 First child inside the transition:slide panel in SearchFilterBar.svelte
Sort section label text-xs font-bold uppercase tracking-widest text-ink-2 mb-3 block 12px / 700 "SORTIEREN NACH"
Sort option pills row flex flex-wrap gap-2 mb-3 Wraps on mobile. 6 pills total.
Inactive sort pill h-9 flex items-center px-4 rounded-full border border-line text-sm font-semibold text-ink-2 cursor-pointer hover:border-brand-navy transition whitespace-nowrap h 36px, px 16px Touch target: min-h-[44px] on mobile. Most commonly undersized.
Active sort pill Same + bg-brand-navy text-white border-brand-navy aria-pressed="true"
Direction row flex gap-2 Two buttons: "↑ Aufsteigend" / "↓ Absteigend"
Direction button h-9 flex items-center gap-1.5 px-4 border border-line text-sm font-bold text-ink-2 rounded-sm cursor-pointer hover:border-brand-navy transition whitespace-nowrap h 36px Active: bg-brand-navy text-white border-brand-navy
Filter button badge count Inline "(N)" text suffix or: absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-brand-navy text-white text-[10px] font-bold flex items-center justify-center border-2 border-surface 16px badge Count = number of active modifiers: active sort (if non-default) + active filter fields. Computed in +page.svelte.
Variant comparison — pick one
Criterion A — Inline B — Pill strip C — Result header D — In filter panel
Discoverability Excellent — always in view Good — visible strip Medium — below search Low — hidden by default
Senior usability Good — labeled button Medium — scroll may confuse Medium — must scroll to find Low — requires two taps to find
Mobile footprint Good — wraps to row 2 Medium — extra full row Excellent — no extra row in search card Excellent — zero chrome when closed
Search bar clarity Medium — adds sort button to row 1 Good — search bar unchanged Excellent — search bar 100% clean Excellent — search bar 100% clean
Active state visibility Excellent — button label changes Excellent — active pill always visible Good — result header shows it Low — only via badge + result count
Implementation complexity Medium — dropdown with dir toggle Low — pills + 2 buttons, no dropdown Medium — new ResultHeader component Low — extends existing filter panel
Keyboard / screen reader Medium — dropdown needs focus trap Excellent — no dropdown, simple buttons Medium — same dropdown focus trap Excellent — inside existing panel flow
Recommended for this project ★ Primary recommendation Strong alternative if row 1 feels crowded Suitable only if sort is secondary Not recommended — discoverability gap
Recommendation — Variant A (Inline) with Variant B (Pill strip) as fallback:
  • Variant A puts sort at 0-click distance, which matters most for seniors and first-time users
  • If the team finds row 1 too dense (especially at 375px), switch to Variant B — it keeps all 6 options visible without a dropdown and has the cleanest keyboard flow
  • Variant C is acceptable if the product decision is that sort is a result-manipulation tool (not a search param). Make the result header sticky so it stays accessible while scrolling
  • Variant D is not recommended: the discoverability deficit is too severe for the senior audience. The badge workaround is a patch, not a solution