Skip to content

Commit e136e6d

Browse files
authored
docs: rename skip link anchor #_top#main-content (WCAG 2.4.1) (#28618)
1 parent 786e457 commit e136e6d

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

docs/src/components/CustomHead.astro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ const filteredHead = head.filter(({ tag, attrs }) => {
8484
import '../scripts/copy-button-aria.ts';
8585
</script>
8686

87+
<!-- Skip link accessibility enhancement: adds id="main-content" and tabindex="-1" to <main> -->
88+
<script>
89+
import '../scripts/skip-link.ts';
90+
</script>
91+
8792
<script is:inline>
8893
(function() {
8994
const storedTheme = localStorage.getItem('starlight-theme') || 'auto';

docs/src/components/SkipLink.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
<!-- Skip navigation link for WCAG 2.4.1 compliance.
55
Visually hidden until focused via keyboard; moves focus directly to the
66
main content area, bypassing repeated navigation blocks. -->
7-
<a href="#_top" class="skip-link">Skip to main content</a>
7+
<a href="#main-content" class="skip-link">Skip to main content</a>

docs/src/scripts/skip-link.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Skip Link Accessibility Enhancement
3+
*
4+
* Starlight renders the main content inside a `<main>` element but does not
5+
* expose a stable `id` on it. The page title heading receives `id="_top"` by
6+
* default, which the custom SkipLink component previously targeted. The name
7+
* `_top` implies "top of page" rather than "main content", creating ambiguity
8+
* for assistive technology users (WCAG 2.4.1).
9+
*
10+
* This script adds `id="main-content"` and `tabindex="-1"` to the `<main>`
11+
* element so that the skip link (`href="#main-content"`) lands on the correct
12+
* landmark and keyboard focus is reliably placed there. The `tabindex="-1"`
13+
* attribute makes the element programmatically focusable without including it
14+
* in the natural tab order.
15+
*
16+
* The enhancement is applied on every page load and re-applied on Astro
17+
* client-side navigation so it works across all pages and navigations.
18+
*/
19+
20+
function enhanceMainLandmark(): void {
21+
const main = document.querySelector<HTMLElement>('main');
22+
if (!main) {
23+
return;
24+
}
25+
if (!main.id) {
26+
main.id = 'main-content';
27+
}
28+
if (!main.hasAttribute('tabindex')) {
29+
main.setAttribute('tabindex', '-1');
30+
}
31+
}
32+
33+
// Run on initial page load
34+
if (document.readyState === 'loading') {
35+
document.addEventListener('DOMContentLoaded', enhanceMainLandmark);
36+
} else {
37+
enhanceMainLandmark();
38+
}
39+
40+
// Re-run on Astro client-side navigation
41+
document.addEventListener('astro:page-load', enhanceMainLandmark);

0 commit comments

Comments
 (0)