`Link`: mark the link to the current page (`aria-current`, `data-active`)

クローズ
#676 コメント 2 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@yangshun がすでに取り組んでいます。

2026年11月3日 から。

  • #687 @yangshun による — マージ済み

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
25/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
react, typescript
領域
frontend

調査の方向性

Start with the shared Link logic that #672 moves into void/pages-client, and read how useRouter() exposes the current path in the React adapter. The open questions are the prop name for prefix matching and whether to match by segment; settle those with maintainers before coding. Done when an exact match sets aria-current="page" and data-active, and activeMatch="prefix" matches whole segments while ignoring query and hash.

索引モデルが issue の本文から書いたものです。

説明

Summary

Link renders a plain <a> with no sign that it points to the current page. Every navigation menu (a header, a sidebar, tabs) has to compare useRouter().path with each link's href by hand. Those hand-written checks usually style the active link but forget aria-current="page", so screen readers aren't told which link is the current page.

Example

What an app writes today:

import { Link, useRouter } from '@void/react';

// The section's home link only matches itself; the others also match their nested pages.
function isActive(path: string, href: string, home: string) {
  return path === href || (href !== home && path.startsWith(`${href}/`));
}

function Sidebar({ links, home }: { links: { href: string; label: string }[]; home: string }) {
  const { path } = useRouter();
  return links.map((link) => {
    const active = isActive(path, link.href, home);
    return (
      <Link
        key={link.href}
        href={link.href}
        data-active={active || undefined}
        aria-current={active ? 'page' : undefined}
      >
        {link.label}
      </Link>
    );
  });
}

What it could be:

<Link href="/admin">Overview</Link>
<Link href="/admin/users" activeMatch="prefix">Users</Link>
Proposal
  • aria-current="page" when the link's path is the current path. It's semantically "this is the current page", so exact match only.
  • data-active on the same link, for styling with [data-active] or Tailwind's data-active:. Unlike a class, it doesn't collide with the app's own className.
  • An opt-in for nested pages, for example activeMatch="prefix", so /admin/users stays highlighted on /admin/users/42. It matches whole path segments, so /admin/users doesn't match /admin/users-archive.
  • Ignore the query and hash when matching, so /inbox?page=2 still marks /inbox.

The names are only suggestions. The router already holds the current path in every adapter, so this should look the same in React, Vue, Svelte and Solid. #672, which moves the shared Link logic into void/pages-client, looks like the natural place for the matching.

Prior art
  • React Router: NavLink sets aria-current="page" and an active class, and takes end to choose between exact and nested matching.
  • TanStack Router: Link sets aria-current="page" and data-status="active", and takes activeOptions: { exact, includeSearch } and activeProps.
  • Vue Router: RouterLink adds router-link-active for nested matches, and router-link-exact-active plus aria-current="page" for exact ones.
  • Nuxt: NuxtLink takes activeClass and exactActiveClass.
Environment
  • void 1.5.0
  • @void/react 1.5.0
主要言語
TypeScript
スター
627
フォーク
66
平均マージ
13時間 57分
マージ済み PR(30日)
194

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

voidzero-dev/void のほかの issue

voidzero-dev/void の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。