facebook / facebook/astryx

Badge: full label should be reachable by focus and on touch, and only when it is actually clipped

Open
#5,585 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
13.1k
Forks
1.1k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

### Context

#5558 stops a long `Badge` label escaping its container by clipping it with an
ellipsis, and — per review — carries a string or number label in the badge's
native `title` so the tail is not simply lost.

`title` is deliberately the cheap half of that answer: it costs no measurement
and no hook, so `Badge` keeps rendering identically on the server. It leaves
three things unsolved.

### Gaps

1. **It is set whether or not the label fits.** Knowing whether the text is
actually clipped requires measuring the rendered element, which `title`
avoids by design. A badge whose label fits comfortably still carries a
redundant tooltip.
2. **It is pointer-only.** A native `title` answers hover. It does not answer
keyboard focus, and on touch it does not appear at all — so the full text is
unreachable for anyone not using a mouse. This is the part of
@rubyycheung's review that #5558 does not close.
3. **A rich `label` gets nothing.** A `ReactNode` label would have to be
flattened to a string, and flattening renders a guess — an icon, a
``, a nested element all read differently — so #5558 leaves it
alone.

### What would close it

A tooltip shown only when the text is really cut:

- measure the label (`scrollWidth > clientWidth`, or a `ResizeObserver`) and
attach the affordance only when it overflows;
- reachable by hover **and** keyboard focus, which means the badge needs to be
focusable when — and only when — it carries recoverable text;
- a touch path, since neither `title` nor hover exists there;
- keep the `title` fallback for the server-rendered pass so the text is never
unreachable before hydration.

That is a client component, which is why #5558 does not do it: it is a real
trade-off (every `Badge` on a page becomes a client boundary, or the tooltip
becomes opt-in) and deserves its own discussion rather than riding along with a
containment fix.

### Not blocking

`Badge` is not interactive today, so making it focusable is a behaviour change,
not just an addition — that is the first thing to settle.

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the Badge implementation and the changes from #5558, then resolve whether the client tooltip belongs in every Badge or is opt-in. Done means overflow-only full-label access works on hover, focus, and touch, rich labels are handled appropriately, and the server-rendered title fallback remains.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
accessibility, design, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.