Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/src/components/CustomHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ const filteredHead = head.filter(({ tag, attrs }) => {
import '../scripts/copy-button-aria.ts';
</script>

<!-- Skip link accessibility enhancement: adds id="main-content" and tabindex="-1" to <main> -->
<script>
import '../scripts/skip-link.ts';
</script>

<script is:inline>
(function() {
const storedTheme = localStorage.getItem('starlight-theme') || 'auto';
Expand Down
2 changes: 1 addition & 1 deletion docs/src/components/SkipLink.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
<!-- Skip navigation link for WCAG 2.4.1 compliance.
Visually hidden until focused via keyboard; moves focus directly to the
main content area, bypassing repeated navigation blocks. -->
<a href="#_top" class="skip-link">Skip to main content</a>
<a href="#main-content" class="skip-link">Skip to main content</a>
41 changes: 41 additions & 0 deletions docs/src/scripts/skip-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Skip Link Accessibility Enhancement
*
* Starlight renders the main content inside a `<main>` element but does not
* expose a stable `id` on it. The page title heading receives `id="_top"` by
* default, which the custom SkipLink component previously targeted. The name
* `_top` implies "top of page" rather than "main content", creating ambiguity
* for assistive technology users (WCAG 2.4.1).
*
* This script adds `id="main-content"` and `tabindex="-1"` to the `<main>`
* element so that the skip link (`href="#main-content"`) lands on the correct
* landmark and keyboard focus is reliably placed there. The `tabindex="-1"`
* attribute makes the element programmatically focusable without including it
* in the natural tab order.
Comment on lines +10 to +14
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says the script “adds id="main-content" and tabindex="-1" to the <main> element”, but the implementation only adds them when the attributes are missing. Either update the comment to reflect the conditional behavior, or change the implementation to always enforce the expected target (#main-content).

This issue also appears on line 25 of the same file.

Suggested change
* This script adds `id="main-content"` and `tabindex="-1"` to the `<main>`
* element so that the skip link (`href="#main-content"`) lands on the correct
* landmark and keyboard focus is reliably placed there. The `tabindex="-1"`
* attribute makes the element programmatically focusable without including it
* in the natural tab order.
* This script ensures the `<main>` element has `id="main-content"` and
* `tabindex="-1"` when those attributes are missing, so the skip link
* (`href="#main-content"`) lands on the correct landmark and keyboard focus is
* reliably placed there. The `tabindex="-1"` attribute makes the element
* programmatically focusable without including it in the natural tab order.

Copilot uses AI. Check for mistakes.
*
* The enhancement is applied on every page load and re-applied on Astro
* client-side navigation so it works across all pages and navigations.
*/

function enhanceMainLandmark(): void {
const main = document.querySelector<HTMLElement>('main');
if (!main) {
return;
}
if (!main.id) {
main.id = 'main-content';
}
if (!main.hasAttribute('tabindex')) {
main.setAttribute('tabindex', '-1');
}
}

// Run on initial page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', enhanceMainLandmark);
} else {
enhanceMainLandmark();
}

// Re-run on Astro client-side navigation
document.addEventListener('astro:page-load', enhanceMainLandmark);
Loading