Skip to content

fix(accessibility): add alt text to PlatformIcon#239

Open
sentry-junior[bot] wants to merge 1 commit into
masterfrom
fix/accessibility-alt-attributes
Open

fix(accessibility): add alt text to PlatformIcon#239
sentry-junior[bot] wants to merge 1 commit into
masterfrom
fix/accessibility-alt-attributes

Conversation

@sentry-junior
Copy link
Copy Markdown

@sentry-junior sentry-junior Bot commented May 26, 2026

What

Adds accessible labels to all PlatformIcon render paths.

Changes

Normal render (<img>):

  • Adds an explicit alt prop to the Props type (note: alt lives on ImgHTMLAttributes, not HTMLAttributes, so it wasn't passable before)
  • Defaults alt to the platform prop value so every icon has a meaningful label without callers needing to change anything
  • Callers can pass alt="" to explicitly mark the icon as decorative when it accompanies visible text

With-language-icon render (composite <div> + two <img>s):

  • Wraps the two images in a div with role="img" and aria-label={alt} (ARIA composite image pattern)
  • Both inner <img>s get alt="" since the parent element is now the accessible entry point

Investigation notes

ContextIcon (in getsentry/sentry at static/app/components/events/contexts/contextIcon.tsx) wraps PlatformIcon and doesn't pass alt. With this change it will inherit alt={platformIconName ?? 'default'} automatically — passable but not ideal. A follow-up in that repo could pass alt={name} (the original context name like "android" or "firefox") for more meaningful labels.

Verification

tsc --noEmit passes clean.


View Session in Sentry

Action taken on behalf of Ryan Albrecht.

- Add explicit `alt` prop to Props type (HTMLAttributes doesn't include it since that's ImgHTMLAttributes)
- Default alt to the platform name so icons are identifiable by screen readers out of the box
- Pass alt="" to mark icons as decorative when caller opts in
- For withLanguageIcon variant: wrap imgs in a div with role="img" and aria-label, inner imgs get alt="" (composite image pattern)
- Document the escape hatch: pass alt="" explicitly to declare the icon decorative

Co-authored-by: Ryan Albrecht <ryan@sentry.io>
Comment thread src/platformIcon.tsx
Comment on lines +328 to +333
<div
role="img"
aria-label={alt}
{...otherProps}
style={{ position: "relative", ...style }}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: When withLanguageIcon is true, passing alt="" to mark an icon as decorative results in an inaccessible div with role="img" and an empty aria-label.
Severity: MEDIUM

Suggested Fix

When withLanguageIcon is true and alt is an empty string, conditionally set role="presentation" or role="none" on the div instead of role="img". This will correctly remove the element from the accessibility tree, aligning its behavior with a native <img alt=""> and ensuring it is treated as purely decorative.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/platformIcon.tsx#L328-L333

Potential issue: When the `PlatformIcon` component is used with the
`withLanguageIcon={true}` prop, it fails to correctly handle decorative images. If a
caller passes `alt=""` to mark the icon as decorative, the component renders a `<div
role="img" aria-label="">`. Unlike a standard `<img alt="">`, this combination is not
consistently treated as decorative by screen readers. This leaves an element with an
empty accessible name in the accessibility tree, leading to unreliable behavior for
users of assistive technology and contradicting the component's documented contract for
handling decorative icons.

Did we get this right? 👍 / 👎 to inform future reviews.

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.

0 participants