Skip to content

feat(accessibility): enhance roadmap components with collapsible functionality and accessibility improvements#5286

Open
codxbrexx wants to merge 1 commit intoasyncapi:masterfrom
codxbrexx:fix/keyboard-nav
Open

feat(accessibility): enhance roadmap components with collapsible functionality and accessibility improvements#5286
codxbrexx wants to merge 1 commit intoasyncapi:masterfrom
codxbrexx:fix/keyboard-nav

Conversation

@codxbrexx
Copy link
Copy Markdown
Contributor

@codxbrexx codxbrexx commented Mar 27, 2026

Description
This improves accessibility on the /roadmap page by making roadmap items work better with keyboard navigation and screen readers.

Changes made:

Replaced description-trigger interactions with proper elements where needed
Added visible keyboard focus styles for interactive roadmap controls
Added ARIA support for expandable roadmap items with aria-expanded, aria-controls, and accessible labels
Wired collapsible roadmap sections to stable IDs using useId()
Added tests to verify the accessibility markup for roadmap interactions

Related issue(s)

#5101

test
image

Summary by CodeRabbit

  • New Features

    • Roadmap items are now collapsible when they contain solutions or implementations.
  • Improvements

    • Enhanced accessibility with improved ARIA labels and controls for better screen reader support.
    • Refined interaction patterns for roadmap titles—links for external URLs, buttons for descriptions.
  • Tests

    • Added comprehensive accessibility test coverage for roadmap components.

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 27, 2026

Deploy Preview for asyncapi-website ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 36eebad
🔍 Latest deploy log https://app.netlify.com/projects/asyncapi-website/deploys/69c6fa5958ac790008d65c5d
😎 Deploy Preview https://deploy-preview-5286--asyncapi-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

The roadmap component's collapsibility logic is enhanced to recognize both solutions and implementations, with a unified collapsible container. The pill component's title rendering is refactored to support distinct rendering paths for URLs, descriptions, and non-interactive states. Accessibility attributes (aria-controls, aria-expanded, aria-label) are added to the collapse button, and React preset support is added to the Babel test configuration.

Changes

Cohort / File(s) Summary
Collapsible Roadmap Components
components/roadmap/RoadmapItem.tsx, components/roadmap/RoadmapPill.tsx
Enhanced collapsibility to treat items as collapsible when either solutions or implementations exist. Title rendering refactored to conditionally render as anchor (with URL), button (triggering description modal), or span. Collapse button improved with type="button" and accessibility attributes (aria-expanded, aria-controls, aria-label). Optional collapsibleContentId prop added to IPillProps.
Test Infrastructure
tests/babel.test.config.cts
Added @babel/preset-react with runtime: 'automatic' to Babel presets for test compilation.
Accessibility Tests
tests/roadmap/accessibility.test.tsx
New test suite verifying accessibility compliance of RoadmapPill and RoadmapItem, including button type, aria attributes, aria-controls references, hidden state, and focus-visible styling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With buttons that speak and containers that hide,
Our roadmap now guides with accessibility pride,
Each aria attribute placed just right,
The collapsible paths now bathed in light! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: enhanced accessibility and collapsible functionality for roadmap components, matching the core modifications across RoadmapItem, RoadmapPill, and new accessibility tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@sonarqubecloud
Copy link
Copy Markdown

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (3a526ff) to head (36eebad).
⚠️ Report is 88 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #5286   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines          796       830   +34     
  Branches       146       159   +13     
=========================================
+ Hits           796       830   +34     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@asyncapi-bot
Copy link
Copy Markdown
Contributor

⚡️ Lighthouse report for the changes in this PR:

Category Score
🔴 Performance 46
🟢 Accessibility 98
🟢 Best practices 92
🟢 SEO 100
🔴 PWA 33

Lighthouse ran on https://deploy-preview-5286--asyncapi-website.netlify.app/

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
tests/roadmap/accessibility.test.tsx (1)

26-61: Add a regression test for implementation-only items.

The production change now treats implementations as collapsible content too, but this suite only exercises the solutions path. A sibling test with only implementations would lock in the new branch this PR added.

Suggested test
+  test('connects implementation-only roadmap items to controlled content', () => {
+    const markup = renderToStaticMarkup(
+      <RoadmapItem
+        item={{
+          title: 'Implementation-only roadmap item',
+          implementations: [{ title: 'Nested implementation' }]
+        }}
+        colorClass='bg-blue-400'
+      />
+    );
+
+    const controlsMatch = markup.match(/aria-controls="([^"]+)"/);
+
+    expect(controlsMatch).not.toBeNull();
+    expect(markup).toContain('aria-expanded="false"');
+    expect(markup).toContain(`id="${controlsMatch?.[1]}"`);
+    expect(markup).toContain('aria-label="Expand Implementation-only roadmap item"');
+    expect(markup).toContain('hidden=""');
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/roadmap/accessibility.test.tsx` around lines 26 - 61, Add a sibling
regression test that mirrors the existing "connects collapsible roadmap items to
controlled content" and "marks collapsible roadmap items as expanded when
children are visible" tests but uses an item with implementations instead of
solutions to exercise the new branch; render RoadmapItem with item={{ title:
'...', implementations: [{ title: 'Impl child' }] }} and the same colorClass and
assertions: check aria-controls exists and the matched id is present,
aria-expanded="false" and aria-label="Expand <title>" and hidden="" for the
collapsed case, and for the expanded case render with collapsed={false} and
assert aria-expanded="true", aria-label="Collapse <title>" and that the
implementation child text ("Impl child") is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/roadmap/RoadmapItem.tsx`:
- Around line 29-30: The collapsible control is enabled when solutions or
implementations fields exist even if they are empty; change the logic in the
isCollapsible computation to check actual child counts (e.g., use
item.solutions?.length and item.implementations?.length) so isCollapsible is
true only when the total number of children > 0, and apply the same count-based
check where the collapse control and content rendering are handled (referencing
isCollapsible, collapsibleContentId and the render block around lines 53-61) so
the toggle is not shown for empty lists.

In `@components/roadmap/RoadmapPill.tsx`:
- Line 77: The non-interactive fallback assigns titleElement to a plain span
with item.title, which loses the completed/checkmark rendering built by
titleContent; update the fallback so it uses the same construction as
titleContent (or delegate to titleContent) when item.done is true so the done
icon/checkmark remains visible for completed items—look for titleElement,
titleContent and the item.done logic in the RoadmapPill component and ensure the
fallback path composes the done state the same way as the primary titleContent.

---

Nitpick comments:
In `@tests/roadmap/accessibility.test.tsx`:
- Around line 26-61: Add a sibling regression test that mirrors the existing
"connects collapsible roadmap items to controlled content" and "marks
collapsible roadmap items as expanded when children are visible" tests but uses
an item with implementations instead of solutions to exercise the new branch;
render RoadmapItem with item={{ title: '...', implementations: [{ title: 'Impl
child' }] }} and the same colorClass and assertions: check aria-controls exists
and the matched id is present, aria-expanded="false" and aria-label="Expand
<title>" and hidden="" for the collapsed case, and for the expanded case render
with collapsed={false} and assert aria-expanded="true", aria-label="Collapse
<title>" and that the implementation child text ("Impl child") is present.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5eb37f6c-cf5a-40da-9bca-f20b27fd3a41

📥 Commits

Reviewing files that changed from the base of the PR and between f51d0b0 and 36eebad.

📒 Files selected for processing (4)
  • components/roadmap/RoadmapItem.tsx
  • components/roadmap/RoadmapPill.tsx
  • tests/babel.test.config.cts
  • tests/roadmap/accessibility.test.tsx

Comment on lines +29 to +30
const isCollapsible = item.solutions !== undefined || item.implementations !== undefined;
const collapsibleContentId = useId();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Base collapsibility on child count, not just field presence.

solutions: [] or implementations: [] currently marks the item as collapsible, but the child lists only render when .length is truthy. That leaves an expand/collapse control wired to an empty container.

Suggested fix
-  const isCollapsible = item.solutions !== undefined || item.implementations !== undefined;
+  const hasSolutions = (item.solutions?.length ?? 0) > 0;
+  const hasImplementations = (item.implementations?.length ?? 0) > 0;
+  const isCollapsible = hasSolutions || hasImplementations;
   const collapsibleContentId = useId();
@@
-          {!isCollapsed && item?.solutions?.length && (
+          {!isCollapsed && hasSolutions && (
             <RoadmapList className='ml-2 pt-3' colorClass='bg-blue-400' items={item.solutions} collapsed={false} />
           )}
 
-          {!isCollapsed && item?.implementations?.length && (
+          {!isCollapsed && hasImplementations && (
             <RoadmapList className='ml-9 pt-3' colorClass='bg-black' items={item.implementations} collapsed={false} />
           )}

Also applies to: 53-61

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/roadmap/RoadmapItem.tsx` around lines 29 - 30, The collapsible
control is enabled when solutions or implementations fields exist even if they
are empty; change the logic in the isCollapsible computation to check actual
child counts (e.g., use item.solutions?.length and item.implementations?.length)
so isCollapsible is true only when the total number of children > 0, and apply
the same count-based check where the collapse control and content rendering are
handled (referencing isCollapsible, collapsibleContentId and the render block
around lines 53-61) so the toggle is not shown for empty lists.

</>
);

let titleElement = <span className='block text-left font-medium text-gray-900'>{item.title}</span>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the done icon in the non-interactive title path.

The fallback branch renders only item.title, so completed pills without a URL or description no longer show the checkmark that titleContent already builds. That’s a visible regression for done roadmap items.

Suggested fix
-  let titleElement = <span className='block text-left font-medium text-gray-900'>{item.title}</span>;
+  let titleElement = <span className='block text-left font-medium text-gray-900'>{titleContent}</span>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/roadmap/RoadmapPill.tsx` at line 77, The non-interactive fallback
assigns titleElement to a plain span with item.title, which loses the
completed/checkmark rendering built by titleContent; update the fallback so it
uses the same construction as titleContent (or delegate to titleContent) when
item.done is true so the done icon/checkmark remains visible for completed
items—look for titleElement, titleContent and the item.done logic in the
RoadmapPill component and ensure the fallback path composes the done state the
same way as the primary titleContent.

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

Labels

None yet

Projects

Status: To Be Triaged

Development

Successfully merging this pull request may close these issues.

2 participants