Skip to content

♿️(frontend) use anchor links for table of contents entries#2390

Open
Ovgodd wants to merge 1 commit into
mainfrom
fix/a11y-2345-toc-links
Open

♿️(frontend) use anchor links for table of contents entries#2390
Ovgodd wants to merge 1 commit into
mainfrom
fix/a11y-2345-toc-links

Conversation

@Ovgodd
Copy link
Copy Markdown
Collaborator

@Ovgodd Ovgodd commented Jun 3, 2026

Purpose

Fix table of contents accessibility: TOC entries were <button> elements without fragment targets, so they were invisible in screen reader link navigation and did not move keyboard focus to the corresponding heading.

Proposal

  • Replace TOC entries with <a href="#heading-{blockId}"> links
  • Sync id and tabindex="-1" on heading blocks in the document (useHeadings)
  • Wrap the TOC list in a <nav> landmark with an accessible label
  • Use aria-current on the active TOC link (instead of invalid aria-selected on links)
  • Keep scroll via BlockNote [data-id] and focus via heading anchors on click
  • Update doc-table-content e2e tests for link role and href / aria-current

@Ovgodd Ovgodd requested a review from AntoLC June 3, 2026 11:45
@Ovgodd Ovgodd self-assigned this Jun 3, 2026
@Ovgodd Ovgodd added bug Something isn't working accessibility triage labels Jun 3, 2026
Replace TOC buttons with anchor links and heading ids for a11y navigation.
@Ovgodd Ovgodd force-pushed the fix/a11y-2345-toc-links branch from 2f43dd1 to f1fe46a Compare June 3, 2026 11:46
@Ovgodd Ovgodd marked this pull request as ready for review June 3, 2026 11:47
@Ovgodd Ovgodd linked an issue Jun 3, 2026 that may be closed by this pull request
@Ovgodd Ovgodd moved this from Backlog to In review in LaSuite Docs A11y Jun 3, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Walkthrough

This PR converts the table-of-contents sidebar from custom button-based navigation to semantic anchor links. Changes include adding DOM synchronization in useHeadings to create and maintain heading IDs and attributes, refactoring the TOC sidebar to use a semantic nav element, replacing BoxButton with anchor elements in the Heading component with proper click handling and aria-current accessibility attributes, and updating e2e tests to validate the new link semantics and ARIA state. A changelog entry documents the frontend improvement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • AntoLC
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: converting table of contents entries from buttons to anchor links for improved accessibility.
Description check ✅ Passed The description is directly related to the changeset, detailing the accessibility problem and the proposed solutions that align with the file changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/a11y-2345-toc-links

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts`:
- Around line 64-74: The test clicks on TOC entries but doesn't assert the new
focus contract; after clicking link1 and link2 add explicit focus assertions
using toBeFocused() on the corresponding heading anchors (i.e., assert
link1.toBeFocused() after the first click and link2.toBeFocused() after the
second click) in the block around the existing viewport/aria-current checks so
the test verifies both visual visibility and keyboard focus transfer for
editorLevel1/editorLevel2 navigation.

In `@src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx`:
- Around line 37-39: In useHeadings (useHeadings.tsx) the logic only checks for
existence of the tabindex attribute; update it to enforce tabindex="-1" whenever
the current value !== "-1" — i.e., read el.getAttribute('tabindex') and call
el.setAttribute('tabindex', '-1') if the value is absent or not equal to '-1' so
TOC anchor focus behavior is normalized; ensure you still use the same el
identifier and setAttribute method used in the diff.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 92b7510b-ac11-4bc9-aabc-ee9a438ac8b1

📥 Commits

Reviewing files that changed from the base of the PR and between a78269a and f1fe46a.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts
  • src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx
  • src/frontend/apps/impress/src/features/docs/doc-table-content/components/Heading.tsx
  • src/frontend/apps/impress/src/features/docs/doc-table-content/components/TableContentSideBar.tsx

Comment on lines +64 to 74
await link1.click();
await expect(editorLevel1).toBeInViewport();
await expect(level1).toHaveAttribute('aria-selected', 'true');
await expect(level2).toHaveAttribute('aria-selected', 'false');
await expect(link1).toHaveAttribute('aria-current', 'true');
await expect(link2).not.toHaveAttribute('aria-current');

await level2.click();
await link2.click();
await expect(editorLevel1).not.toBeInViewport();
await expect(editorLevel2).toBeInViewport();
await expect(level2).toHaveAttribute('aria-selected', 'true');
await expect(level1).toHaveAttribute('aria-selected', 'false');
await expect(link2).toHaveAttribute('aria-current', 'true');
await expect(link1).not.toHaveAttribute('aria-current');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Assert focus transfer after TOC activation.

On Line 64 and Line 69, clicks currently validate viewport and aria-current, but not the new focus-move contract. Add explicit toBeFocused() assertions on the heading anchors to lock in the accessibility fix.

Suggested test diff
@@
     await expect(link1).toHaveAttribute('href', /^`#heading-/`);
     await expect(link2).toHaveAttribute('href', /^`#heading-/`);
     await expect(link3).toHaveAttribute('href', /^`#heading-/`);
+    const heading1Anchor = page.locator((await link1.getAttribute('href'))!);
+    const heading2Anchor = page.locator((await link2.getAttribute('href'))!);
@@
     await link1.click();
     await expect(editorLevel1).toBeInViewport();
+    await expect(heading1Anchor).toBeFocused();
     await expect(link1).toHaveAttribute('aria-current', 'true');
     await expect(link2).not.toHaveAttribute('aria-current');
@@
     await link2.click();
     await expect(editorLevel1).not.toBeInViewport();
     await expect(editorLevel2).toBeInViewport();
+    await expect(heading2Anchor).toBeFocused();
     await expect(link2).toHaveAttribute('aria-current', 'true');
     await expect(link1).not.toHaveAttribute('aria-current');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts` around
lines 64 - 74, The test clicks on TOC entries but doesn't assert the new focus
contract; after clicking link1 and link2 add explicit focus assertions using
toBeFocused() on the corresponding heading anchors (i.e., assert
link1.toBeFocused() after the first click and link2.toBeFocused() after the
second click) in the block around the existing viewport/aria-current checks so
the test verifies both visual visibility and keyboard focus transfer for
editorLevel1/editorLevel2 navigation.

Comment on lines +37 to +39
if (!el.hasAttribute('tabindex')) {
el.setAttribute('tabindex', '-1');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce tabindex="-1" value, not only attribute presence.

Line 37 only checks whether tabindex exists. If it exists with a different value, this won’t normalize focus behavior for TOC anchors. Set it whenever the value is not -1.

Suggested patch
-    if (!el.hasAttribute('tabindex')) {
+    if (el.getAttribute('tabindex') !== '-1') {
       el.setAttribute('tabindex', '-1');
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!el.hasAttribute('tabindex')) {
el.setAttribute('tabindex', '-1');
}
if (el.getAttribute('tabindex') !== '-1') {
el.setAttribute('tabindex', '-1');
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/apps/impress/src/features/docs/doc-editor/hook/useHeadings.tsx`
around lines 37 - 39, In useHeadings (useHeadings.tsx) the logic only checks for
existence of the tabindex attribute; update it to enforce tabindex="-1" whenever
the current value !== "-1" — i.e., read el.getAttribute('tabindex') and call
el.setAttribute('tabindex', '-1') if the value is absent or not equal to '-1' so
TOC anchor focus behavior is normalized; ensure you still use the same el
identifier and setAttribute method used in the diff.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 3, 2026

Size Change: +1 B (0%)

Total Size: 4.33 MB

📦 View Changed
Filename Size Change
apps/impress/out/_next/static/6a48b439/_buildManifest.js 674 B +674 B (new file) 🆕
apps/impress/out/_next/static/f2e4aa77/_buildManifest.js 0 B -673 B (removed) 🏆

compressed-size-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility bug Something isn't working triage

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Table of contents: elements implemented as buttons instead of links

1 participant