feat(Avatar): optional href/onClick interactivity + roving focus in AvatarGroup (#4170)#4225
Draft
cixzhang wants to merge 1 commit into
Draft
feat(Avatar): optional href/onClick interactivity + roving focus in AvatarGroup (#4170)#4225cixzhang wants to merge 1 commit into
cixzhang wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
github-actions
Bot
requested review from
cvkxx,
ernestt,
kentonquatman and
rubyycheung
July 23, 2026 00:34
Contributor
Author
Self-review — Quick Self-Review checklist (Build Protocol)
Spec compliance notes
Deviation (justified): changeset bump is |
Contributor
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR Modified ComponentsAvatar · View in Storybook
AvatarGroup · View in Storybook
Bundle Size Summary
Accessibility AuditStatus: 2 accessibility violation(s) found — 2 serious. Avatar - 1 issue(s)
AvatarGroup - 1 issue(s)
Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds optional interactivity to
Avatarand single-tab-stop roving focus toAvatarGroup.Avatar — a
href/onClickelement-swap that mirrorsButtonexactly:hrefset → renders a link via theuseLinkComponent(as)hook (respectsLinkProvider; never a hardcoded<a>).as/target/relare forwarded.onClickset → renders a<button type="button">.role="img", orrole="presentation"+aria-hiddenwhen decorative). Non-breaking default.Prop names are copied verbatim from
Button(href/as/target/rel/onClick) — nointeractive/clickablemode prop. Interactive avatars get the system focus-visible accent ring and a real accessible name (alt/name).AvatarGroup — when it contains interactive avatars (or an interactive
AvatarGroupOverflow), it becomes a single Tab stop with roving ArrowLeft/ArrowRight focus, and announces a screen-reader keyboard hint. A purely static facepile is untouched.Why
Avatars are frequently linked to profiles or used as buttons, and consumers have been wrapping them in their own
<a>/<button>— which drops the focus ring, the accessible name, and (in a group) tab-management. Baking the exactButtontrichotomy in keeps the API familiar and the a11y correct.How
Buttontrichotomy. Same resolution order, same prop names, sameuseLinkComponenthook for router-aware links. No new mode prop — the presence ofhref/onClickdrives the rendered element.useListFocus({hasRovingTabIndex, orientation: 'horizontal', isRtl, itemSelector}). The hook already owns the roving tabindex (onetabindex=0, the rest-1) and is the same primitiveButtonGroup/SegmentedControl/Toolbaruse — no new mode was added.data-avatar-itemmarker as theitemSelector. Roving selects on this marker (stamped on the rendered interactive<a>/<button>and onAvatarGroupOverflow's button), not a tag/role selector — so a nested<button>inside a custom status/badge slot never joins the roving order.aria-describedbyon therole="group"root, sourced from a new i18n key (@astryx.avatarGroup.keyboardHint). It is only rendered/attached when the group actually has interactive children — no hint on a static facepile.getComputedStyle(el).direction === 'rtl'), then passed touseListFocus.console.warn— matching the established client-safe warning pattern in sibling components (Field/Timestamp/Popover). It is not gated onprocess.env(not available on the client in this codebase).Deferred
clickAction(Button's async prop) is intentionally not added here — it's a follow-up.Testing
href) / button (onClick) / static (neither);hrefwins overonClick; focus + ring on interactive;data-avatar-itemonly on interactive; dev-warn on interactive-without-name; no warn for static-decorative.aria-describedbyhint appears only with interactive children.Closes #4170