Skip to content

fix(a11y): WCAG 2.1 accessibility improvements across Icon component and demo - #6

Merged
yacosta738 merged 6 commits into
mainfrom
fix/accessibility-wcag-improvements
May 16, 2026
Merged

fix(a11y): WCAG 2.1 accessibility improvements across Icon component and demo#6
yacosta738 merged 6 commits into
mainfrom
fix/accessibility-wcag-improvements

Conversation

@yacosta738

Copy link
Copy Markdown

Summary

This PR fixes several WCAG 2.1 accessibility violations found in the Icon component and the demo pages. Changes are grouped into three focused commits for easier review.


Issues Fixed

packages/core/components/Icon.astro — Critical

Issue WCAG Criterion Fix
_id variable used in template but computed as _id<symbol> and <use> referenced an undefined id Bug Renamed _idid, _normalizedBodynormalizedBody
Decorative SVGs announced by screen readers without a name 1.1.1 (A) Added aria-hidden="true" when no title prop is provided
Meaningful icons had no accessible role 4.1.2 (A) Added role="img" when title prop is provided
SVGs focusable in IE/Edge and some Chromium builds 2.1.1 (A) Added focusable="false" unconditionally

demo/src/layouts/base.astro — Serious

Issue WCAG Criterion Fix
No <main> landmark — AT users couldn't navigate to main content 1.3.6, 2.4.1 (A) Wrapped <slot /> in <main id="main-content" tabindex="-1">
No skip link — keyboard users must tab through all repeated content 2.4.1 (A) Added visible-on-focus skip link targeting #main-content
No <meta name="description"> Added page description

demo/src/pages/index.astro and map.astro — Moderate

Issue WCAG Criterion Fix
Standalone icons used without title — invisible to AT 1.1.1 (A) Added title prop to each meaningful icon
_icon variable shadowed — runtime reference would fail Bug Renamed _iconicon
Star icons rendered as flat list — group meaning lost 1.1.1, 1.3.1 (A) Wrapped in <figure aria-label="Rating: 4 out of 5 stars"> + visually-hidden <figcaption>
No explicit imports Added explicit import statements for Base and Icon

How to Verify

Keyboard

  • Tab through both pages — focus should never be trapped or lost
  • Activate the skip link (Tab once then Enter) — focus moves to <main>

Screen reader (VoiceOver / NVDA)

  • Decorative icons must not be announced
  • Icons with title must be announced as "<title text>, image"
  • Star rating group must read "Rating: 4 out of 5 stars" (or equivalent)

Automated

npx @axe-core/cli http://localhost:4321 --exit
npx pa11y http://localhost:4321 --reporter cli

WCAG References


Accessibility review:

  • Semantics/roles/names: ✅ Fixed — role="img" + title for meaningful icons; aria-hidden for decorative
  • Keyboard & focus: ✅ Fixed — skip link + <main> landmark; focusable="false" on SVGs
  • Announcements (async/route): N/A for this PR
  • Contrast/visual focus: ✅ No regressions — skip link has sufficient contrast on focus
  • Forms/errors/help: N/A for this PR
  • WCAG 2.2 (target size, dragging, focus obscured, auth): N/A for this PR

- Rename _id to id so symbol/use references resolve correctly (bug fix)
- Rename _normalizedBody to normalizedBody to match template usage (bug fix)
- Add focusable='false' to prevent SVGs from being keyboard-focusable
  in IE/Edge and some Chromium builds (WCAG 2.1.1)
- Decorative icons (no title prop) receive aria-hidden='true' so
  assistive technologies skip them (WCAG 1.1.1)
- Meaningful icons (with title prop) receive role='img' so screen
  readers expose the title as the accessible name (WCAG 4.1.2)
- Associate desc element with a stable id for future aria-describedby
  wiring (WCAG 1.1.1)
…se layout

- Wrap slot in <main id='main-content'> to provide a navigation
  landmark that keyboard and AT users can target (WCAG 1.3.6, 2.4.1)
- Add skip link that becomes visible on focus so keyboard users can
  bypass repeated header content (WCAG 2.4.1 - Level A)
- Add meta description for better AT context and SEO
- Skip link uses tabindex='-1' on <main> to allow programmatic focus
  without disrupting natural tab order
…cs in demo pages

- Add explicit imports for Base layout and Icon component to replace
  implicit globals; makes component contracts clear and auditable
- Fix variable name: _icon -> icon so the runtime reference resolves
- Add title prop to standalone icons so they are exposed as role='img'
  with an accessible name to screen readers (WCAG 1.1.1)
- Wrap star icons in <figure aria-label='Rating: 4 out of 5 stars'>
  with a visually-hidden <figcaption> so the group meaning is
  communicated to AT users (WCAG 1.1.1, 1.3.1)
- Add .visually-hidden utility class following the standard SR-only
  clip pattern
@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@yacosta738 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 53 minutes and 37 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3092aee0-e409-48ed-b6ca-0c4735d773d4

📥 Commits

Reviewing files that changed from the base of the PR and between 64ede0f and 6bd197f.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • demo/src/pages/index.astro
  • demo/src/pages/map.astro
  • packages/core/components/Icon.astro
📝 Walkthrough

Walkthrough

This PR enhances icon accessibility across the component library and demo. The core Icon component now conditionally applies ARIA attributes based on whether a title is provided, generates stable IDs for description linking, and enforces focusable="false". The demo layout adds a skip-to-main-content navigation link and document metadata, while demo pages illustrate proper icon accessibility patterns using title props and CSS utility classes for visually-hidden content.

Changes

Icon Accessibility Enhancement

Layer / File(s) Summary
Icon Component Accessibility Infrastructure
packages/core/components/Icon.astro
Icon.astro generates stable IDs from icon collection and name, forces focusable="false", and conditionally sets aria-hidden="true" (for decorative icons) or role="img" (when a title prop is provided). The desc element is annotated with a matching ID to link descriptions.
Layout Navigation & Document Metadata
demo/src/layouts/base.astro
Base layout adds a meta description tag and implements an accessible skip-to-main-content link with CSS :focus styling. The main content region uses tabindex="-1" to receive focus from the skip link.
Page Examples: Accessible Icon Patterns
demo/src/pages/index.astro, demo/src/pages/map.astro
Index page renames the icon constant and adds title props to remote icons with explanatory comments about decorative vs. meaningful icon accessibility. Map page replaces inline star rendering with a figure element containing per-icon title text and a visually-hidden figcaption, supported by a .visually-hidden CSS utility.

Sequence Diagram

sequenceDiagram
  participant DemoPage as Demo Page
  participant IconComponent as Icon Component
  participant DOMOutput as DOM Output
  DemoPage->>IconComponent: Props (icon, title?)
  IconComponent->>IconComponent: Generate ID from collection/name
  alt title provided
    IconComponent->>IconComponent: Set role="img"
    IconComponent->>DOMOutput: Render with aria-label/title
  else no title
    IconComponent->>IconComponent: Set aria-hidden="true"
    IconComponent->>DOMOutput: Render as decorative
  end
  IconComponent->>DOMOutput: Add desc with matching ID
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A skip-link hops ahead with grace,
Icons now speak their rightful place,
Titles and roles in harmony,
Screen readers dance so joyfully,
Accessibility's the way to play! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change—WCAG 2.1 accessibility improvements across the Icon component and demo—which aligns with the substantial accessibility fixes detailed in the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing specific WCAG violations fixed, affected files, verification steps, and relevant accessibility standards—all aligned with the changes made.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/accessibility-wcag-improvements

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.

actions/checkout on pull_request events checks out the merge commit,
leaving the runner in detached HEAD state. A plain 'git push' then
fails with exit 128 because there is no active branch to push to.

Fix: use 'git push origin HEAD:${HEAD_REF}' when github.head_ref is
set (PR context), falling back to plain 'git push' on direct pushes
to main where HEAD is always attached.

@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: 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 `@demo/src/pages/map.astro`:
- Around line 15-19: The per-star title prop on the Icon instances inside the
Array.from(...) in map.astro makes each star announced separately; remove the
per-star title and mark the icons decorative instead—either pass
aria-hidden="true" to each <Icon> or add a boolean decorative prop (e.g.,
decorative={true}) and update Icon.astro (which currently sets role="img") to
render role="img" only for non-decorative icons and use aria-hidden or
role="presentation" for decorative ones so screen readers only read the overall
rating/figure.

In `@packages/core/components/Icon.astro`:
- Around line 116-125: The SVG never references the stable description id
created (desc id), so when a description exists and the icon is not decorative
you must add an aria-describedby attribute to the normalizedProps so the <svg>
is programmatically bound to the <desc>; update the normalizedProps object (used
to render the <svg>) to include something like aria-describedby: descId (only
when descId is defined and isDecorative is false) while keeping the existing
aria-hidden/role logic in the isDecorative branch and ensuring the attribute is
applied alongside focusable and other attrs in normalizedProps.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59b9c41b-dc92-4105-890c-c7d4a0ff89b4

📥 Commits

Reviewing files that changed from the base of the PR and between 790e940 and 64ede0f.

📒 Files selected for processing (4)
  • demo/src/layouts/base.astro
  • demo/src/pages/index.astro
  • demo/src/pages/map.astro
  • packages/core/components/Icon.astro

Comment thread demo/src/pages/map.astro Outdated
Comment thread packages/core/components/Icon.astro
- Add biome-ignore on normalizedBody variable (used in Astro template,
  outside Biome JS scope) to prevent false unused-variable warning
- Apply biome import order: sort imports alphabetically in demo pages
- Apply biome ternary formatting in Icon.astro (cosmetic only)
- Use --force-with-lease on push to guard against concurrent pushes
  while still protecting against accidental overwrite of others' work
- Icon.astro: add aria-describedby to normalizedProps when desc is
  provided and icon is meaningful, binding <svg> to its <desc id>
  (WCAG 1.1.1 / 4.1.2)
- map.astro: remove per-star title props so each icon is decorative;
  the <figure aria-label> and <figcaption> carry the collective meaning
  and screen readers no longer announce each star separately
@sonarqubecloud

Copy link
Copy Markdown

@yacosta738
yacosta738 merged commit af292bc into main May 16, 2026
7 of 8 checks passed
yacosta738 added a commit that referenced this pull request May 16, 2026
- Add explicit permissions blocks to all CI jobs (actions/missing-workflow-permissions)
  - lint job: contents: write (needed for git push on auto-fix)
  - smoke job: contents: read (read-only, follows least privilege)
- Add pnpm override for yaml >= 2.8.3 to patch stack overflow vulnerability
  (GHSA-xxx, yaml >= 2.0.0 < 2.8.3)

Closes code-scanning alerts #6 and #7
Closes dependabot alert #13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant