Components: Add Avatar and AvatarGroup private components#75591
Components: Add Avatar and AvatarGroup private components#75591dabowman wants to merge 6 commits intoWordPress:trunkfrom
Conversation
Introduces two new private components in @wordpress/components: - Avatar: A circular avatar image with configurable size and optional accent border color, driven by CSS custom properties. - AvatarGroup: Displays stacked avatars with negative-margin overlap and a "+N" overflow indicator when children exceed a configurable max. Both are registered as private APIs for internal core usage and include Storybook stories and unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change size prop from arbitrary number to 'default' | 'small' (32px and 24px, matching $button-size-compact and $button-size-small) - Default avatar renders with no border; borderColor is fully opt-in - When borderColor is set, apply 1.5px accent border with white inset ring, matching the Figma "has border=true" variant - Use $radius-full, $border-width-tab, $gray-700, and $white tokens to match design variables exactly - Add overflow: clip on base avatar for borderless image clipping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends the Avatar component with several new features: - Initials fallback: derives up to two letters from `name` when no `src` image is provided. - `badge` prop (opt-in): hover-expand pill that reveals the user's name or a custom `label` override. - `status` prop: dims the avatar via luminosity blend mode (`active` at 20% opacity, `idle` at 30%) to signal non-default states. - `statusIndicator` prop: accepts any `IconType` value and renders it as a centered overlay when `status` is set. - Suppresses badge, shadow, and status indicator inside AvatarGroup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make AvatarGroup a pure layout container that no longer suppresses Avatar styling (shadows, badges, status indicators). Add inline z-index stacking so the first child renders on top, with hover elevation for expanding badges. Replace the max-width badge animation with CSS Grid 0fr→1fr interpolation for smooth width transitions without layout bumps. Add Tooltip for the name when badge mode is inactive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
- Remove built-in `.is-active` status override; all statuses now get
uniform dimming so the consumer controls differentiation via the
dynamic `is-{status}` class.
- Preserve Tooltip on badge avatars when `label` differs from `name`,
so hovering reveals the full name even when the badge shows a
contextual label like "You".
- Replace `cloneElement` z-index injection in AvatarGroup with a CSS
`@for` loop on `:nth-child`, removing the legacy React API and its
type assertion.
- Add `role="group"` to AvatarGroup for screen reader context;
consumers provide `aria-label` via props.
- Add `aria-label` to the overflow indicator so "+2" reads as
"2 more".
- Add MixedBadgeAndTooltip story covering badge, tooltip, and label
combinations in a group.
- Improve JSDoc: clarify `badge` requires `name`, describe `status`
as an open-ended styling hook.
- Add CHANGELOG entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0c186ab to
7d77380
Compare
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @dabowman! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
|
This component is needed for Real-time Collaboration and for the Notes features. I made a clean PR here with just the component so we can discuss it in isolation, but I've also added the code to the PR that refines the RTC UI over here #75595 . If need be we can relocate the component code in that PR into the editor package where the RTC UI lives. That way it's not making an addition to the components package if we want more time to review this before making an addition. |
|
Looping in @WordPress/gutenberg-components here for review and awareness. It looks like an initial version of this landed already in #75595 . It'd be good to get some eyes from the broader components group, though as a private API it gives us some buffer to iterate. Aside from being used in real-time collaboration, I could imagine many use-cases of a component like this for general reusability, and is common in design systems (e.g. we've been using a lot of BaseUI recently, which also includes an Avatar component). It'd be good to try to stabilize and shift this toward consistency with other components in |
| /** | ||
| * A status string applied to the avatar. When set, the image is | ||
| * dimmed to indicate a non-default state. A corresponding | ||
| * `is-{status}` class is added for custom styling. | ||
| */ | ||
| status?: string; |
There was a problem hiding this comment.
If this moves to @wordpress/ui, we may want to consider the reliance on CSS modifier class names like is being introduced here, since the components implemented there heavily depend on CSS modules that have randomly-generated class names, for the expressed purpose of preventing this kind of ad hoc style extensibility (citation needed).
Instead, I feel like if we want to support a stylized effect of dimming, that should be baked-in to the component. Or maybe support some customization through composability (nested components, etc.). It's not entirely clear to me how this is intended to be used, since it doesn't appear to be used yet with real-time collaboration based on what I see in #75595.
There was a problem hiding this comment.
yeah, it's not being used yet. The addition of the status-based class was mostly just to have the option of customizing things beyond what the component already does. But it makes sense that we don't want that. We also don't really need it for the way this is intended to be used. I think I'll just remove the class generation, especially since it's not being used yet.
There was a problem hiding this comment.
I changed this to a basic "dimmed" option. So it's style specific and doesn't add arbitrary css anymore.
| /** | ||
| * Name of the user. Used as an accessible label and to generate | ||
| * initials when no image is provided. | ||
| */ | ||
| name?: string; | ||
| /** | ||
| * Visible text shown in the hover badge. When not provided, `name` | ||
| * is used instead. Use this to provide contextual labels like "You" | ||
| * without affecting the accessible name or initials. | ||
| */ | ||
| label?: string; |
There was a problem hiding this comment.
I'm wondering about alignment between these props and how we think of the label prop in IconButton, where we intentionally try to discourage mismatch between the accessible name and the tooltip content, though I think the use-case of something like "You" is interesting to consider, and feels similar in purpose to the sort of suffixing that was implemented in IconButton's shortcut support (e.g. maybe still deriving tooltip from name, but allowing the option for additional suffixing detail).
There was a problem hiding this comment.
Yeah, I'm happy to name things whatever y'all think is best. The intent was just to have one prop that provides the accessible name that's used for the tooltip and generating any fallback initials. This would be the "main" name or label or whatever. In addition to that I wanted to provide a secondary label/name/whatever to use contextually if we need to call a collaborator/user/entity something different. I can see this being useful when agents or other connected agentic things start appearing as collaborators. Instances where an entities exact name is less important than the role they're playing in the given situation. Happy to align that with how the button or other components handle that difference in meaning.
| /** | ||
| * Whether to show the hover-expand badge that reveals the user's | ||
| * name (or `label`) on hover. Requires `name` to be set. | ||
| * | ||
| * @default false | ||
| */ | ||
| badge?: boolean; |
There was a problem hiding this comment.
The way this affects the visual presentation of the avatar reminds me of how we use variant props in other components (e.g. Button variant "describes the visual style treatment"). Though I think this surfaces the need for documented consistency of how we use these common props.
There was a problem hiding this comment.
Yeah, making this an enum instead of a bool makes a lot of sense. Then we could have other secondary displays for the component. I saw mentions of other status "badges" like green dots for connected or online, things like that. That stuff might fall under this category that just becomes a broader "variant" prop.
There was a problem hiding this comment.
changed this to a variant enum. badge is currently the only option but we could add more
| /** | ||
| * Size of the avatar. | ||
| * | ||
| * - `'default'`: 32px | ||
| * - `'small'`: 24px | ||
| * | ||
| * @default 'default' | ||
| */ | ||
| size?: 'default' | 'small'; |
There was a problem hiding this comment.
From a systems perspective, I think it'd be helpful if we provided some guidance on when each size should be used, similar to what we do in Button, rather than simply communicating the raw pixel size and expecting someone to figure it out on their own.
There was a problem hiding this comment.
Yeah, totally. The documentation needs to be more prescriptive here.
There was a problem hiding this comment.
fixed the docs here
| /** | ||
| * CSS color value for an accent border ring around the avatar. | ||
| * | ||
| * When not provided, no border is rendered and the hover badge | ||
| * and avatar status uses the admin theme color as its background. | ||
| */ | ||
| borderColor?: string; |
There was a problem hiding this comment.
Interesting to think how we might want to think about token usage here rather than hard-coded values. In other places we've expressly moved away from supporting hard-coded values in favor of tokens (example). Although in how this is used in real-time collaboration with a bunch of different colors for each collaborator, I'm not sure we can really tokenize this as-is, without either baking in tokens for complementary colors (like what you're exploring in #74875), or wrapping every Avatar with ThemeProvider (excessive), or allowing hard-coded values as you're considering here.
There was a problem hiding this comment.
Yeah, ideally this is locked to tokens, but realistically I think it needs to support arbitrary colors. Especially since different uses might need to generate their own colors. If we could provide a method for doing that from the theme package we might be able to tighten that up. But until then it probably needs to accept hex values.
Update Avatar to match the refined API from the improve/real-time-collaboration-ui-polish branch: - Replace `badge` boolean with `variant` enum prop - Replace `status` string with `dimmed` boolean - Add contrast-aware badge text color via colord - Rename class/CSS-variable collisions (has-border-color → has-avatar-border-color, --components-avatar-border-color → --components-avatar-outline-color) - Move background-color from root to badge variant - Move status indicator outside __image for dimmed opacity isolation - Update stories and tests to match Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| } | ||
|
|
||
| // Initials fallback: show name characters when no image. | ||
| .components-avatar:not(.has-src) > .components-avatar__image { |
There was a problem hiding this comment.
If we go with the initials fallback it'd be worth to loop in the design team, so that we can have a pass on how it'd look like for that case.
There was a problem hiding this comment.
Yeah, the initials here are following the designs they worked on for Notes and RTC. So it should be in line with what they expect.
Summary
Adds two new private components to
@wordpress/componentsfor rendering user avatars, intended for use in real-time collaboration UI and other internal features.maxand overflow indicator (+N), CSS-based z-index stacking, androle="group"for accessibility.Both are registered as private APIs via
lock(privateApis, { Avatar, AvatarGroup })— not exposed publicly.Key design decisions
statusprop accepts any string and applies uniform dimming. A dynamicis-{status}class is added for consumer-specific styling. No built-in status-specific overrides.labelorname. Whenlabeldiffers fromname, a Tooltip also wraps the avatar so the full name is accessible on hover.@forloop on:nth-childinstead ofcloneElementfor stacking order.grid-template-columns: 0fr → 1frfor exact natural width expansion (avoidsmax-widthovershoot).prefers-reduced-motiondisables transitions.$components-color-accent,$elevation-x-small,$radius-full,$button-size-compact,$grid-unit-*, etc. — no hardcoded values.Test plan
npm run storybook:dev→ Design System/Components/Avatar and AvatarGroup)status-privatetag excludes stories from public Storybook🤖 Generated with Claude Code