Skip to content

fix(website): structural accessibility (landmarks, icon-link names, dropdown state)#5469

Open
mmabrouk wants to merge 1 commit into
mainfrom
fix/website-a11y-structural
Open

fix(website): structural accessibility (landmarks, icon-link names, dropdown state)#5469
mmabrouk wants to merge 1 commit into
mainfrom
fix/website-a11y-structural

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

The constraint

Structural and semantic accessibility only. Every change is invisible: colors, contrast, spacing, and layout render exactly as before. Color and contrast findings are deliberately left untouched per founder decision.

This was verified by pixel-diffing full-page screenshots before and after, at 1440 and 412 widths, across the landing, pricing, blog index, and a blog post.

Pixel-identity proof

Captures were stabilized by forcing prefers-reduced-motion (so the scroll-driven "How it works" renders its deterministic static layout) and freezing CSS animations (the logo marquee).

Page 1440 412
Landing byte-identical byte-identical
Pricing byte-identical byte-identical
Blog post byte-identical byte-identical
Blog index identical* identical*

* The blog index initially differed only inside the fixed card frames, where loading="lazy" hero images had decoded in one capture but not the other. Re-captured with all images deterministically decoded, both widths are byte-identical. To isolate this from the code change, the "before" blog index was rebuilt from main (no <main> wrapper) and compared against the "after" build with the same image-load protocol.

Lighthouse accessibility (landing, desktop)

Before: 87 → After: 96.

Failing audits resolved:

  • landmark-one-main — document had no <main> landmark.
  • link-name — 20 logo-only marquee links had no accessible name.
  • aria-prohibited-attr — 4 provider icon <span>s carried aria-label on a generic role.

The single remaining Lighthouse failure is color-contrast, which is out of scope by founder decision.

What changed (structural only)

  1. Landmarks. Added a <main> landmark. In Site.astro it wraps the page <slot>; on the landing it wraps the section stack. The nav (<nav>) and footer (<footer>) were already landmarks. The <main> replaces no styled node and introduces no style, so margin-collapse and layout are preserved (proven by the byte-identical diffs).
  2. Icon-link names. The "MCPs & tools" marquee row is logo-only; each link now carries an aria-label (GitHub, Slack, PostgreSQL, and so on). Duplicated marquee copies stay aria-hidden.
  3. Dropdown state. The nav's hover/focus dropdowns reported a static aria-expanded="false". A small script now mirrors the CSS open state (:hover / :focus-within) onto the trigger's aria-expanded. Semantic only; the CSS still drives what is shown.
  4. Same-class extras. role="img" on the four provider icon spans (makes their existing aria-label valid); title on 9 YouTube iframes across 8 blog posts.

Out of scope and untouched: all color and contrast findings. lang was already present; the interactive islands (hosting toggle, category filter, template explorer) were already labeled.

Screenshots

Before/after full-page captures and the pixel-diff artifacts are saved under debug/website-a11y-qa/.

https://claude.ai/code/session_013Qe1Vf2yvj33BVd5W1q7HB

…dropdown state

Zero visual change (pixel-verified). Color/contrast deliberately untouched
per founder decision.

Claude-Session: https://claude.ai/code/session_013Qe1Vf2yvj33BVd5W1q7HB
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. Frontend Improvement labels Jul 23, 2026
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 23, 2026 4:25pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Website preview

Preview URL: https://pr-5469-agenta-website-preview.mahmoud-637.workers.dev

Built from 0dfb3a4f2ca2a62103c751e8524edbae04ebe114. This comment updates in place on every push.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Accessibility Improvements
    • Improved screen reader support for icon-only chips, model icons, navigation dropdowns, and embedded videos.
    • Added semantic <main> landmarks to improve page structure and navigation.
    • Improved dropdown state announcements for keyboard and assistive technology users.
  • Documentation
    • Added descriptive titles to video demonstrations across product and feature articles.

Walkthrough

The website adds semantic main landmarks, synchronizes navigation dropdown aria-expanded state, supplies accessible names for icon-only controls, marks model icons as images, and adds titles to embedded YouTube videos.

Changes

Website accessibility refinements

Layer / File(s) Summary
Page landmark structure
website/src/layouts/Site.astro, website/src/pages/index.astro
Page content is wrapped in <main> elements in the shared layout and landing page.
Dropdown expanded-state synchronization
website/src/components/SiteNav.astro
Dropdown trigger buttons update aria-expanded based on pointer and focus events.
Chip and model icon naming
website/src/components/OpenStandards.astro, website/src/components/TemplateExplorer.tsx
MCP/tool chip data supplies labels for icon-only links, and model icon wrappers receive role="img".
Embedded video titles
website/src/content/posts/*.mdx
YouTube iframe embeds receive descriptive title attributes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the accessibility-focused website changes, including landmarks, icon link names, and dropdown state.
Description check ✅ Passed The description directly matches the accessibility-only structural changes and accurately describes the scope and results.
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.
✨ 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/website-a11y-structural

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dc92a71c-800d-4fcc-bd89-b34c113cfa87

📥 Commits

Reviewing files that changed from the base of the PR and between 297eb1e and 0dfb3a4.

📒 Files selected for processing (13)
  • website/src/components/OpenStandards.astro
  • website/src/components/SiteNav.astro
  • website/src/components/TemplateExplorer.tsx
  • website/src/content/posts/introducing-custom-workflows.mdx
  • website/src/content/posts/introducing-prompt-registry.mdx
  • website/src/content/posts/july-2025-product-updates.mdx
  • website/src/content/posts/launch-week-2-day-1.mdx
  • website/src/content/posts/launch-week-2-day-2-online-evaluation.mdx
  • website/src/content/posts/launch-week-2-day-3-evaluation-sdk.mdx
  • website/src/content/posts/prompt-playground.mdx
  • website/src/content/posts/structured-outputs-playground.mdx
  • website/src/layouts/Site.astro
  • website/src/pages/index.astro

Comment on lines +227 to +233
dd.addEventListener("mouseenter", () => sync(true));
dd.addEventListener("mouseleave", () => sync(false));
dd.addEventListener("focusin", () => sync(true));
dd.addEventListener("focusout", () => {
// focusout fires before focus lands on the next target; defer so we can
// read whether focus is still inside this dropdown.
requestAnimationFrame(() => sync(dd.contains(document.activeElement)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive aria-expanded from both hover and focus state.

mouseleave can set aria-expanded="false" while focus remains inside the dropdown, and deferred focusout can do the same while the pointer still hovers. Since the CSS keeps the panel open when either :hover or :focus-within matches, the ARIA state can contradict the visible state. Make sync evaluate both selectors instead of accepting a single boolean.

Proposed fix
-    const sync = (open: boolean) =>
-      trigger.setAttribute("aria-expanded", open ? "true" : "false");
-    dd.addEventListener("mouseenter", () => sync(true));
-    dd.addEventListener("mouseleave", () => sync(false));
-    dd.addEventListener("focusin", () => sync(true));
+    const sync = () =>
+      trigger.setAttribute(
+        "aria-expanded",
+        dd.matches(":hover, :focus-within") ? "true" : "false",
+      );
+    dd.addEventListener("mouseenter", sync);
+    dd.addEventListener("mouseleave", sync);
+    dd.addEventListener("focusin", sync);
     dd.addEventListener("focusout", () => {
-      requestAnimationFrame(() => sync(dd.contains(document.activeElement)));
+      requestAnimationFrame(sync);
     });
📝 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
dd.addEventListener("mouseenter", () => sync(true));
dd.addEventListener("mouseleave", () => sync(false));
dd.addEventListener("focusin", () => sync(true));
dd.addEventListener("focusout", () => {
// focusout fires before focus lands on the next target; defer so we can
// read whether focus is still inside this dropdown.
requestAnimationFrame(() => sync(dd.contains(document.activeElement)));
const sync = () =>
trigger.setAttribute(
"aria-expanded",
dd.matches(":hover, :focus-within") ? "true" : "false",
);
dd.addEventListener("mouseenter", sync);
dd.addEventListener("mouseleave", sync);
dd.addEventListener("focusin", sync);
dd.addEventListener("focusout", () => {
// focusout fires before focus lands on the next target; defer so we can
// read whether focus is still inside this dropdown.
requestAnimationFrame(sync);
});

@mmabrouk
mmabrouk enabled auto-merge July 23, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Frontend Improvement size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant