From 8b348510107d26c414e54398284a968fbecfed82 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Sat, 21 Mar 2026 13:20:25 +0100 Subject: [PATCH] Add shell accessibility landmarks --- ...es_frontend_overhaul_implementation_plan.md | 4 +++- .../Components/Shell/AppShell.razor | 10 ++++++---- .../Components/Shell/AppShell.razor.css | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/tables_frontend_overhaul_implementation_plan.md b/docs/tables_frontend_overhaul_implementation_plan.md index 87ca5e5..ac77258 100644 --- a/docs/tables_frontend_overhaul_implementation_plan.md +++ b/docs/tables_frontend_overhaul_implementation_plan.md @@ -44,6 +44,7 @@ It is intentionally implementation-focused: | 2026-03-21 | P1.4 | Completed | Added shared browser-storage wrappers, persisted theme mode in `localStorage`, and initialize/apply theme state from the layout on interactive render. | | 2026-03-21 | P1.5 | Completed | Replaced the permanent sidebar layout with a sticky top app shell and mobile bottom navigation backed by dedicated shell components. | | 2026-03-21 | P1.6 | Completed | Added explicit shell slots for nav, omnibox, shortcuts, and utilities; switched shell navigation to `Play`, `Tables`, `Curation`, and `Tools`; and wired the first live theme control into the shell. | +| 2026-03-21 | P1.7 | Completed | Added a shell-level skip link and tightened the top-level header, navigation, and main landmarks around the new shell structure. | ### Lessons Learned @@ -57,6 +58,7 @@ It is intentionally implementation-focused: - Shared UI state events in Blazor should stay synchronous unless the event contract is async-aware. Layout refresh can be triggered safely with `InvokeAsync(StateHasChanged)` from a synchronous handler. - Extracting shell markup into dedicated components is lower-risk than continuing to evolve `MainLayout.razor` directly. It isolates responsive frame work from page content and keeps later nav changes localized. - Once a Razor component exposes multiple named `RenderFragment` parameters, the page body must be passed explicitly through ``. That pattern is now the baseline for shell composition here. +- Accessibility work is cheaper when the shell owns the landmarks. Adding skip links and nav/main structure at the shell layer avoids repeating that work page by page. ## Target Outcomes @@ -264,7 +266,7 @@ Create the implementation foundation so the visual overhaul does not start with | `P1.4` | Completed | Theme mode now persists through a shared storage service and is applied from the layout during interactive startup. | | `P1.5` | Completed | The sidebar is gone; pages now render inside a sticky top-shell with a mobile bottom nav. | | `P1.6` | Completed | The shell now has explicit nav, omnibox, shortcut, and utility slots, plus a live theme selector and destination-model navigation. | -| `P1.7` | Pending | Skip link and landmark work will land with the shell markup. | +| `P1.7` | Completed | The shell now exposes a skip link and explicit header/nav/main landmarks. | | `P1.8` | Pending | Tools-specific shell emphasis is final Phase 1 polish. | ### Goal diff --git a/src/RolemasterDb.App/Components/Shell/AppShell.razor b/src/RolemasterDb.App/Components/Shell/AppShell.razor index 289eea0..78dfe76 100644 --- a/src/RolemasterDb.App/Components/Shell/AppShell.razor +++ b/src/RolemasterDb.App/Components/Shell/AppShell.razor @@ -1,4 +1,6 @@
+ +
@@ -18,7 +20,7 @@ }
-
+
+
@if (OmniboxContent is not null) @@ -43,9 +45,9 @@ @if (ShortcutContent is not null) { -
+
+ }
diff --git a/src/RolemasterDb.App/Components/Shell/AppShell.razor.css b/src/RolemasterDb.App/Components/Shell/AppShell.razor.css index 5efebb0..c63cff0 100644 --- a/src/RolemasterDb.App/Components/Shell/AppShell.razor.css +++ b/src/RolemasterDb.App/Components/Shell/AppShell.razor.css @@ -4,6 +4,24 @@ flex-direction: column; } +.skip-link { + position: absolute; + left: 1rem; + top: -3rem; + z-index: 80; + padding: 0.75rem 1rem; + border-radius: 999px; + background: var(--accent-5); + color: var(--text-on-accent); + text-decoration: none; + box-shadow: var(--shadow-1); + transition: top 140ms ease; +} + +.skip-link:focus { + top: 1rem; +} + .app-shell-header { position: sticky; top: 0;