From 7cd4830e8ef05648c8e0aad26ae30a9702bc99af Mon Sep 17 00:00:00 2001 From: Yangshun Date: Tue, 3 Nov 2026 20:20:42 +0800 Subject: [PATCH 1/2] feat(pages): mark the Link to the current page `Link` renders `aria-current="page"` and `data-void-active` when it points to the current page, in all four adapters. `activeMatch="prefix"` keeps a link active on nested pages; only an exact match gets `aria-current`. Matching lives in `linkActiveAttributes` in `void/pages-client` and follows the router: pathnames compare case-sensitively with trailing slashes significant, the query and hash are ignored, and links to other origins or with non-GET methods never match. A passed `aria-current` or `data-void-active` overrides the computed value. Closes #676 Co-Authored-By: Claude Opus 5.5 --- docs/guide/pages-routing/overview.md | 59 +++++++++++++++ docs/reference/api/pages.md | 82 ++++++++++----------- packages/react/src/runtime/link.tsx | 8 ++ packages/react/test/unit/link.test.ts | 33 ++++++++- packages/solid/src/runtime/link.tsx | 7 ++ packages/svelte/src/runtime/Link.svelte | 5 ++ packages/void/skills/void/SKILL.md | 3 + packages/void/src/pages/client.ts | 3 + packages/void/src/pages/link.ts | 54 ++++++++++++++ packages/void/test/unit/pages-link.test.ts | 66 +++++++++++++++++ packages/vue/src/runtime/link.ts | 8 ++ packages/vue/test/unit/link.typecheck.ts | 5 +- playground/react-pages/pages/layout.tsx | 2 +- playground/solid-pages/pages/layout.tsx | 2 +- playground/svelte-pages/pages/layout.svelte | 2 +- playground/vue-pages/pages/layout.vue | 3 +- test/pages-client-contract.ts | 48 ++++++++++++ 17 files changed, 343 insertions(+), 47 deletions(-) diff --git a/docs/guide/pages-routing/overview.md b/docs/guide/pages-routing/overview.md index 08ad304eb..004b9a3ef 100644 --- a/docs/guide/pages-routing/overview.md +++ b/docs/guide/pages-routing/overview.md @@ -265,6 +265,65 @@ export default function PostPage() { } ``` +### Active links + +`Link` marks the link to the current page with `aria-current="page"` and `data-void-active`. Use them to style navigation: + +```css +nav a[data-void-active] { + font-weight: 600; +} +``` + +A link is active only on its own path by default. Set `activeMatch="prefix"` to keep it active on nested pages: + +::: code-group + +```tsx [React] + +``` + +```vue [Vue] + +``` + +```svelte [Svelte] + +``` + +```tsx [Solid] + +``` + +::: + +| Current URL | `href="/users"` | `href="/users" activeMatch="prefix"` | +| --------------- | --------------- | ------------------------------------ | +| `/users` | both attributes | both attributes | +| `/users?page=2` | both attributes | both attributes | +| `/users/42` | none | `data-void-active` | +| `/users-admin` | none | none | + +`aria-current="page"` marks only the exact page, so select `[aria-current="page"]` to style it differently from its section. A link to `/` is active only on the home page. + +Paths match the way pages are routed: letter case and trailing slashes count, while the query string and hash are ignored. Links to other origins and non-GET links are never active. An `aria-current` or `data-void-active` you pass to `Link` replaces the computed value. + ## Scroll Restoration Void manages scroll position for Pages automatically: diff --git a/docs/reference/api/pages.md b/docs/reference/api/pages.md index b577e75ee..dd10cd0fd 100644 --- a/docs/reference/api/pages.md +++ b/docs/reference/api/pages.md @@ -53,16 +53,16 @@ interface VoidVueOptions { Imported from `"@void/vue"`. -| Export | Description | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Link` | Vue component for SPA navigation. Renders `` for GET, `