diff --git a/.Jules/palette.md b/.Jules/palette.md index 8321d34..63893f1 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -10,6 +10,10 @@ **Learning:** When building interactive card components that contain multiple pieces of text or badges (like titles and photo counts), screen readers may announce them in a fragmented or confusing way. **Action:** Always apply a comprehensive `aria-label` to the parent link/container that encompasses all meaningful visual data (e.g., titles and item counts). Use `aria-hidden="true"` on redundant text or visual child elements, and set `alt=""` on decorative images to consolidate screen reader announcements into a single, accurate interaction point. +## 2025-05-07 - Skip-to-Content Navigation +**Learning:** Keyboard users and screen readers need a way to bypass repetitive header navigation links. Without a visually hidden skip link that becomes visible on focus, these users must tab through every nav item before reaching the main content on every page load. +**Action:** Always include a "Skip to content" link as the first focusable element in the DOM, anchored to the main content container. Ensure the main container has `tabIndex={-1}` so it can programmatically receive focus without interrupting normal tab flow. + ## 2024-06-18 - Dynamic Numeric Counters ARIA **Learning:** When displaying dynamic numeric text changes during navigation (like a lightbox image counter), the visual presentation (e.g., "1 / 5") does not provide sufficient context for screen reader users and might not be announced automatically. **Action:** Wrap the counter container with `aria-live="polite"` and `aria-atomic="true"`. Use `.sr-only` to provide a context-rich screen reader string (e.g., "Photo 1 of 5") and hide the visual short-hand using `aria-hidden="true"`. diff --git a/app/globals.css b/app/globals.css index 67b610c..65af6fe 100644 --- a/app/globals.css +++ b/app/globals.css @@ -7,6 +7,27 @@ @import url('./tokens.css'); +/* ── Skip to content link ─────────────────────────── */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--bg-card); + color: var(--text-primary); + padding: 8px 16px; + z-index: 1000; + transition: top 0.2s ease; + border-bottom-right-radius: var(--radius-sm); + font-family: var(--font-sans); + font-weight: 500; + font-size: 0.85rem; +} + +.skip-link:focus { + top: 0; + outline: 2px solid var(--text-primary); +} + /* ── Header / Navigation ──────────────────────────── */ .header { position: fixed; diff --git a/app/layout.tsx b/app/layout.tsx index 15362eb..c6ef554 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -90,6 +90,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) + + Skip to content +
-
{children}
+
+ {children} +