fix(avatar): pair each status-dot variant with a distinct shape#4157
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR Analysis ReportModified ComponentsAvatar
Bundle Size Summary
Accessibility AuditStatus: 1 accessibility violation(s) found — 1 serious. Avatar - 1 issue(s)
Generated by PR Enrichment workflow | View full report |
|
Thanks for tackling this — the accessibility direction is right, and we do want to support shape-differentiated status dots (not colour alone). Approving the intent; one thing to refine before it lands. The glyph weight feels off against the rest of the system. Right now the ring band and minus bar are ~25% of the glyph's inner field at every tier (2px on an 8px field, 4px on 16px, 6px on 24px). That's heavier than any other glyph we ship, which is why they read as a bit clunky rather than "system-native." For comparison, our existing inline SVG glyphs are much lighter and use stroked paths with round caps:
Two suggestions to make the glyphs feel cohesive:
Net: keep the shape vocabulary (filled / ring / minus) exactly as you have it — just lighten the stroke and move to stroked SVG so it matches how the rest of the system draws glyphs. |
AvatarStatusDot conveyed status by colour alone, failing WCAG 2.1 Level A SC 1.4.1 (Use of Color). Each variant now pairs its colour with a built-in shape: success stays the filled dot, neutral renders a ring, error a minus bar — surface-coloured cutouts that stay legible at every size tier, including the smallest where icons cannot render. A rendered icon (isRenderable, not truthiness) replaces the glyph; custom augmented variants are documented as needing their own non-colour mark. The glyph is a stable theming target: astryx-avatar-status-dot-glyph + data-shape. Also fixes two pre-existing doc bugs surfaced by review: an error dot labelled Online in the AvatarShowcase CLI block, and a wrong slot element (StatusDot / invalid variant) in Avatar.doc.mjs. Fixes facebook#4143
…h weight (facebook#4143) Review feedback: the ring band and minus bar were ~25% of the dot's inner field at every tier, several times heavier than any other glyph we ship. Draw them as stroked inline SVG instead of CSS box cutouts — the same zero-dependency approach as Icon's defaultIcons and the CheckboxInput checkmark — which buys sub-pixel stroke control and round line caps that a box cannot give at a 10px dot. Stroke is now roughly field/12 floored at 1px, landing on the system's 1 / 1.5 / 2 ladder. A thin stroke only reads as a ring if the interior is not the variant colour, so `neutral` inverts to a surface plate with a coloured stroke. Everything drawn on the dot — glyph and any user `icon` — now paints from the dot's `currentColor`, so an icon on the ring variant stays legible instead of rendering surface-on-surface. Shape vocabulary (filled / ring / minus) is unchanged. Verified at true 1x under deuteranopia, protanopia and achromatopsia: all three shapes still separate without colour at the smallest tier.
b30ebd6 to
17a6610
Compare
|
Thanks for the detailed review. I checked all three of your points against the code before changing anything, and they all hold up:
So I have done both things you asked. The glyphs are now stroked inline SVG (a Stroke ladder. I went with 1 / 1.5 / 2px across the three tiers, whose inner fields are 8, 16 and 24px. That is your One knock-on effect worth flagging. A thin stroke only reads as a ring if the interior is not the variant colour, otherwise it reads as a bullseye. So The accessibility check. Since the point of this PR is that shape carries the status, I did not want to take a lighter stroke on trust. I rendered old and new at true 1x, magnified nearest-neighbour so I was looking at real device pixels, under deuteranopia, protanopia and achromatopsia, in light and dark. All three shapes still separate with no colour at all, including at the smallest tier. The ring actually reads better than it did: at 10px the old thick band looked like a filled blob with a pinhole, and the new one is unmistakably an outline. One honest caveat. A 1px stroke cannot pixel-centre in an 8px field, so at 1x the smallest minus bar anti-aliases slightly softer than the old 2px bar. It is exactly 2 device pixels and crisp at 2x. That is the direct trade for the sub-pixel control, and it still reads clearly in the achromatopsia renders, but I would rather name it than hide it. I also rebased past #4140, so the size scale rename is picked up and the branch is mergeable again. The shape vocabulary is unchanged, as you asked. Note that the vocabulary itself (ring for neutral, minus for error) is still waiting on a call from @cixzhang, since it changes how shipped |
|
Hey @AKnassa — 👋 I'm the author of #4218, which tackles the same WCAG 1.4.1 issue on AvatarStatusDot from the icon+label angle. @humbertovirtudes reviewed it and suggested we converge, pointing to your PR as the stronger base (and I agree — the SVG shapes work at every size, which icons can't do at the smallest tier). What I'd like to propose: Your shapes approach is solid. What's missing (and what my PR adds) is a default accessible label per variant, so the status is never conveyed by colour alone even when shapes aren't visible or the dot is inside a Avatar root. Specifically:
I'm happy to close #4218 and fold this label piece into your PR if you're open to it. Want me to open a PR against your branch, or would you rather cherry-pick the relevant parts? Also worth noting: your PR already documents that the label isn't announced inside Avatar today (because Avatar root is Let me know what works best for you! |
There was a problem hiding this comment.
This is a lovely fix. Pairing each variant with a distinct shape topology (filled dot, ring, minus bar) so status never rides on color alone is exactly the resolution the component needed, and drawing the glyph as a stroked inline SVG in {currentColor} is what makes it hold up at the 10px tier where a CSS box cutout can only land on whole pixels.
Highlights:
- The {currentColor} discipline: painting both the glyph and any user icon from the same ink so plate and mark can never drift out of contrast, and inverting the neutral variant to a surface plate so a hollow ring actually reads as hollow.
- Honesty about the boundary: documenting that a
labelon the dot is not announced inside Avatar'srole="img"and flagging it as a planned Avatar-level follow-up, rather than ignoring it. - A rendered
iconsuppressing the built-in glyph (two cutouts in one tiny field would make both illegible) is the right precedence. - Semantic tokens preserved throughout, and a stable
astryx-avatar-status-dot-glyphclass plusdata-shapeso themes have a real hook.
One non-blocking note: the GLYPH_STROKE_WIDTH ladder comment is on the long side. The math is non-obvious so some of it earns its place, but it could tighten a touch. Not worth holding the PR tho.
nice work.
Yall can follow up on a separate PR now that this one is merged :P Keeping changes compact makes for easier reviews Thanks for following up! |
|
Followed up on a separate PR as suggested — #4298 adds the default accessible labels ("Online"/"Away"/"Busy") on top of the shapes base that landed here. @humbertovirtudes would you mind taking a look when you have a moment? Added you as a reviewer. |
Fixes #4143.
What this does
Avatar status dots used to differ only by colour — green for online, grey for away, red for busy. For colour-blind users those can look identical. Now each status also has its own shape:
The same shapes Slack, Teams, and Discord use, so people already know what they mean.
Why
WCAG 2.1 Level A (SC 1.4.1) says colour can't be the only way information is conveyed. The issue's colour-vision simulations show green and red collapsing into the same olive. With shapes, the status reads even with no colour perception at all.
What changed
iconstill takes over as the visual mark when it renders (docs now say to use a different icon per status).astryx-avatar-status-dot-glyph+data-shape.labelisn't announced inside an Avatar today (the Avatar root isrole="img") — composing it into the avatar's name is a planned follow-up.How to see it
Storybook → Core/Avatar → Status Shapes at Small Sizes (new story). Screenshots attached below, including colour-blindness simulations, light and dark.
Kept as draft on purpose
The ring/minus shapes change how shipped
neutralanderrordots look (the defaultsuccessis unchanged). That visual vocabulary deserves a maintainer's okay before this goes ready.Note: the standalone
StatusDotcomponent has the same colour-only pattern across five variants — a follow-up issue for it is being filed separately.