Skip to content

feat!: Revamped Avatar component#1048

Merged
pawelgrimm merged 41 commits into
mainfrom
pawel/avatar-component-api
May 28, 2026
Merged

feat!: Revamped Avatar component#1048
pawelgrimm merged 41 commits into
mainfrom
pawel/avatar-component-api

Conversation

@pawelgrimm
Copy link
Copy Markdown
Contributor

@pawelgrimm pawelgrimm commented May 26, 2026

Summary

We are bringing the Avatar component up to spec with the design, including:

  • Support for specific sizes
  • Circle and rounded shapes
  • Dynamic image URL selection (based on srcset)
  • Image load failure handling
  • Wider array of initials/meta colors
  • Accessibility handling for custom or decorative labels

We also added a new documentation page for the component, which walks through all the features it supports. The new and improved stories feature all the contributors to this repo from the past year 😁

We also removed some features that we don't really need:

  • media query-based responsive sizing
  • Deriving initials from email (you can just pass the user's email as name, if desired)

Sorry for the rather large PR — I'd split it up, but that would mean pushing accompanying stories, docs, and tests in a separate PR, which isn't great either. Lmk if you feel strongly about having a smaller PR in this case and I can do my best to rework it.

References

📸 Demo

BeforeAfter
CleanShot 2026-05-27 at 11 57 05 CleanShot 2026-05-27 at 11 57 19

PR Checklist

  • Added tests for bugs / new features
  • Updated docs (storybooks, readme)
  • Reviewed and approved Chromatic visual regression tests in CI

@pawelgrimm pawelgrimm force-pushed the pawel/avatar-component-api branch from a953da4 to 8f6ff49 Compare May 27, 2026 16:30
@pawelgrimm pawelgrimm changed the title feat: add avatar component feat!: Revamped Avatar component May 27, 2026
@pawelgrimm pawelgrimm self-assigned this May 27, 2026
@pawelgrimm pawelgrimm added the 🙋 Ask PR Used for PRs that need a review before merging. label May 27, 2026
@pawelgrimm pawelgrimm marked this pull request as ready for review May 27, 2026 16:51
@pawelgrimm pawelgrimm requested review from a team and rmartins90 and removed request for a team May 27, 2026 16:56
Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

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

Thanks pawelgrimm for your contribution 😊. The new fallback handling and dynamic image selection look solid, and the updated documentation featuring contributor avatars is a great touch.

Few things worth tightening:

  • Wrap the component in React.forwardRef and restore the ...props rest spread to the root element to maintain a flexible, standard API.
  • Derive the default fallback label from the normalized name instead of the raw prop so whitespace-only inputs don't yield a blank accessible name.
  • Add a base jest-axe test to the suite per our guidelines, alongside a targeted test verifying that custom alt text is correctly exposed on the initials fallback.

I also included a few optional follow-up notes in the details below.

Optional follow-up notes (6)
  • [P3] src/avatar/avatar.stories.tsx:9: This duplicates the supported-size list that already exists as AVATAR_SIZES in utils.ts. If the component contract changes, the stories/playground can drift from the actual runtime/type surface. Import the shared constant here instead of maintaining a second copy.
  • [P3] src/avatar/utils.ts:74: normalizeAvatarName already replaces all whitespace sequences with a single space. Using .split(' ') here produces identical results and avoids running the Unicode regex engine a second time.
  • [P3] src/avatar/utils.ts:147: Consider adding a fast-path if (failedSources.length === 0) return imageProps; before checking imageProps.sources. This avoids re-filtering the array and reconstructing the image properties object on every render for the typical happy path where no image sources have failed.
  • [P3] src/avatar/avatar.tsx:102: getInitials(name) and getAvatarMetaColorIndex(name) now run on every render, even when availableImageSources is truthy and the component never uses the initials/meta-color path. For image-heavy avatar lists, that adds avoidable normalization/segmentation/hash work to the hot path. Computing these only when we actually fall back to initials would avoid that per-render overhead.
  • [P3] src/avatar/avatar.mdx:25: This docs page now renders all of the image-heavy canvases inline, and most of those stories use live GitHub avatar URLs. Opening Avatar.mdx will trigger roughly 26 external image requests up front, which makes Storybook/Chromatic slower and less deterministic than it needs to be. Using local fixtures (or trimming the number of inline canvases) would avoid that resource overhead.
  • [P3] src/avatar/avatar.tsx:113: This new metaColor-* class family is camel-cased, but Reactist’s CSS naming convention is dash-separated (variant-primary, size-small, etc.). Please rename these lookups/classes to something like meta-color-* so Avatar stays consistent with the rest of the library.

Share FeedbackReview Logs

Comment thread src/avatar/avatar.tsx Outdated
Comment thread src/avatar/avatar.test.tsx
Comment thread src/avatar/avatar.tsx Outdated
Comment thread src/avatar/avatar.tsx Outdated
Comment thread src/avatar/avatar.test.tsx
@pawelgrimm pawelgrimm mentioned this pull request May 28, 2026
3 tasks
Copy link
Copy Markdown
Contributor

@rmartins90 rmartins90 left a comment

Choose a reason for hiding this comment

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

Left 3 minor comments @pawelgrimm. Other than that, it looks good to me

Comment thread src/avatar/avatar.tsx Outdated
Comment thread src/avatar/avatar.tsx Outdated
Comment thread src/avatar/avatar.test.tsx
Comment thread src/avatar/avatar.mdx
@pawelgrimm pawelgrimm force-pushed the pawel/avatar-component-api branch from f86eb30 to 30fca01 Compare May 28, 2026 20:12
@pawelgrimm pawelgrimm merged commit fbf1e54 into main May 28, 2026
10 checks passed
@pawelgrimm pawelgrimm deleted the pawel/avatar-component-api branch May 28, 2026 20:44
doist-release-bot Bot pushed a commit that referenced this pull request May 28, 2026
## [32.0.0](v31.3.0...v32.0.0) (2026-05-28)

### ⚠ BREAKING CHANGES

* Revamped Avatar component (#1048)

### Features

* Revamped Avatar component ([#1048](#1048)) ([fbf1e54](fbf1e54))
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 32.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@doist-release-bot doist-release-bot Bot added the Released PRs that have been merged and released label May 28, 2026
@scottlovegrove
Copy link
Copy Markdown
Contributor

@pawelgrimm Don't the changes in this now mean the Avatar component is no longer responsive? What if I'm looking at the web app on mobile? I'm now getting the same avatar size as on desktop, right?

v32 drops the responsive size API. In v31 size accepted ResponsiveProp<AvatarSize> would render smaller avatars on mobile and larger on desktop from a single element. v32's size is a required single number and also drives srcSet selection, so there's no supported way to vary it per viewport short of duplicating the avatar behind media queries or hacking --reactist-avatar-size via exceptionallySetClassName. Was dropping responsive sizing intentional? If so, what's the recommended migration for responsive avatars?

@pawelgrimm
Copy link
Copy Markdown
Contributor Author

@pawelgrimm Don't the changes in this now mean the Avatar component is no longer responsive? What if I'm looking at the web app on mobile? I'm now getting the same avatar size as on desktop, right?

@scottlovegrove Yes 😅

v32 drops the responsive size API. In v31 size accepted ResponsiveProp<AvatarSize> would render smaller avatars on mobile and larger on desktop from a single element. v32's size is a required single number and also drives srcSet selection, so there's no supported way to vary it per viewport short of duplicating the avatar behind media queries or hacking --reactist-avatar-size via exceptionallySetClassName. Was dropping responsive sizing intentional? If so, what's the recommended migration for responsive avatars?

I dropped the responsive sizing intentionally since 1) we weren't using it internally anywhere, and 2) it would have made the srcSet selection and some of the CSS a lot more complicated, so I decided to drop it.

I'm not at all opposed to you adding it back in, but if I were you, I'd just duplicate the avatar behind media-query driven containers OR dynamically select a size in JS based on the screen size.

@scottlovegrove
Copy link
Copy Markdown
Contributor

I dropped the responsive sizing intentionally since ... we weren't using it internally anywhere

sigh 🙄

I'm not at all opposed to you adding it back in, but if I were you, I'd just duplicate the avatar behind media-query driven containers OR dynamically select a size in JS based on the screen size.

How does this not go against the principles of this library? It should be responsive, everything was built for responsiveness, it's a web app UI package!

@gnapse
Copy link
Copy Markdown
Contributor

gnapse commented Jun 3, 2026

We also removed some features that we don't really need:

  • media query-based responsive sizing

I'm also a bit concerned about removing functionality in this way.

Does this mean then that we're going to remove the ResponsiveProp<T> feature entirely? Or was it just for the Avatar? If it's the latter, then why just the avatar? Why is this feature not bothering in the other places where it is currently supported?

@pawelgrimm
Copy link
Copy Markdown
Contributor Author

How does this not go against the principles of this library? It should be responsive, everything was built for responsiveness, it's a web app UI package!
Does this mean then that we're going to remove the ResponsiveProp feature entirely? Or was it just for the Avatar? If it's the latter, then why just the avatar? Why is this feature not bothering in the other places where it is currently supported?

I’m sorry this blocked you from upgrading, @scottlovegrove. I prepared #1058 for you, which restores the previous Avatar implementation as DeprecatedAvatar while we figure out the stable API for the newer component. Feel free to review and merge it if it’s useful to you.

A robust component library should bias towards responsiveness, I won't disagree with you there, but I genuinely thought I was acting in line with the existing conventions in this library. Reactist supports responsive behavior today in that we have a responsive prop system, but it is applied selectively. From what I can see, responsive props are used exclusively in the layout primitives (Box, Inline, Stack, and Columns). There are plenty of components with size-type props that are not responsive: Button, Loading, Spinner, and even Text and Heading. I’d argue responsive typography sizing is at least as important to responsive UI as Avatar sizing, but Text::size and Heading::size are both static today.

Responsive sizing on Avatar seemed more similar to the latter set of component, so I dropped it given the new implementation complexity around numeric sizes and srcSet/sizes, not to mention the forthcoming avatar groups (#1051, #1052).

I'm also a bit concerned about removing functionality in this way.

I think you raise a good point, @gnapse! Should we add and maintain features in Reactist that we don’t use internally? If yes, why and what criteria should we use for deciding that? How should we handle deprecating functionality?

Given the renewed interest in this library, I think it's a good time to codify our stance on these kinds of principles. It'll be much easier to make calls in a consistent and predictable manner. @frankieyan Is this something you'd be interested in taking point on as part of your component modernization work?

@frankieyan
Copy link
Copy Markdown
Member

Yeah, I can bring this up with the designers and see where responsiveness stands for us. I would prefer that we roll the responsive prop into the new Avatar if it doesn't overly complicate it, over having to keep both copies around for long, as I've just started to remove some of our older deprecated components. @pawelgrimm would this make the follow-up avatar groups difficult to implement? The last thing I'd want to do is to delay your timelines.

Baking responsiveness into our products would make better experiences. It's never something we spent alot of effort on beyond making it serviceable, and comparing Todoist's mobile app vs mobile web, our (web) avatars, which aren't even using Reactist components, are definitely undersized. That said, I'd like to get product and design's input first and ensure our apps can/will make use of them before we invest our time here.

Ultimately, I think we should cater to our products' requirements first and include a similar callout as Typist:

Whilst everyone is welcome to fork or use this package in their own products, development decisions are centered around Doist product requirements.

And if we end up deciding mobile web experiences are not something we want to improve in the near term, I'll make that explicit both here in Reactist and in the handbook.

@pawelgrimm
Copy link
Copy Markdown
Contributor Author

pawelgrimm commented Jun 4, 2026

@pawelgrimm would this make the follow-up avatar groups difficult to implement?

@frankieyan That's exactly what I'm afraid of. I'll try reworking the API to make responsive sizing fit in, but then I might have to abandon the approach I'm taking to clip avatars and just require consumers to pass in the background color.

I'm talking about the space between each avatar in the group, as you see here:
CleanShot 2026-06-04 at 17 37 49@2x

One approach is to use a background-colored border around each avatar. The other is to clip the avatars so that the background content is shown through the gap. I thought the latter had much better ergonomics... at the cost of massively complicated CSS 🙈

The last thing I'd want to do is to delay your timelines.

Oh don't worry, I've already de-prioritized those components for now 😅 I was planning on picking them back up when I have some more slack.

@frankieyan
Copy link
Copy Markdown
Member

One approach is to use a background-colored border around each avatar. The other is to clip the avatars so that the background content is shown through the gap. I thought the latter had much better ergonomics... at the cost of massively complicated CSS 🙈

Do we know what the designers' intentions are? Currently in both Todoist and Twist, these stacked avatars are implemented with borders:

image

Not saying it looks better this way, but it'd be simpler, and are already implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Ask PR Used for PRs that need a review before merging. Released PRs that have been merged and released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants