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.
?sort=date&dir=desc| 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. |
-webkit-mask-image: linear-gradient(to right, #000 80%, transparent)) to the pill container to signal overflow.
| 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" |
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.| 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. |
| 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. |
| 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 |