# 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
FAMILIENARCHIV 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
FAMILIENARCHIV
⌕
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

Element | Tailwind classes | Real size | Notes
--- | --- | --- | ---
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
FAMILIENARCHIV 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
FAMILIENARCHIV
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

Element | Tailwind classes | Real size | Notes
--- | --- | --- | ---
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
FAMILIENARCHIV 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
FAMILIENARCHIV
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

Element | Tailwind classes | Real size | Notes
--- | --- | --- | ---
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
FAMILIENARCHIV 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
FAMILIENARCHIV 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

Element | Tailwind classes | Real size | Notes
--- | --- | --- | ---
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