Skip to content

fix(Typeahead): the value token's button is named by the field label and the value - #9049

Merged
cixzhang merged 5 commits into
mainfrom
lane4/typeahead-token-named
Oct 31, 2026
Merged

cixzhang merged 5 commits into
mainfrom
lane4/typeahead-token-named

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

User impact

  • Before: with a value, Typeahead takes its input out of the Tab order, so the Tab stop is the value's token button. That button was named by the value alone. A screen-reader user tabbing into a form heard "Apple, button", with no field name, description or error, and could not tell which of several Typeaheads it belonged to.
  • After: the token button is named by the field label and the value ("Fruit Apple, button"; with a caller aria-labelledby, that label joins the field label as it does on the input), and described by the field's description and status message, like the input.
  • Unchanged: the visible token, edit mode, and the empty field (the combobox).

Fixes #8945.

Spec

The specification session decided this: the field label names the field's primary control (family:input-fields FR10 and Field's labelling), so the token button holding the value is named by the field label plus the value and described by the field's description and status (WCAG 2.4.6, 4.1.2). Typeahead.spec.md is draft.

Changes

  • Typeahead.tsx:
    • the Field label gets an id;
    • the token button is named as the input is (the field label, or the input-group labels, plus a caller aria-labelledby when given), plus a hidden span holding the value;
    • it is aria-describedby the same ids as the input.
  • Token.tsx: a clickable token passes aria-labelledby and aria-describedby to its inner button, the element that owns its action (architecture:public-component-api INV5). Before, they landed on the generic root span, where they named nothing. Other structures keep them on the root.
  • Token.spec.md (current): FR10 and AR2 record that a clickable token's aria-labelledby and aria-describedby reach its inner button; the FR10 verification row checks it. A disabled link token keeps them on its root, with a test.
  • Tests:
    • Typeahead: the Tab stop with a value is named "Fruit Apple" and described by the description and the error;
    • Token: the naming props reach a clickable token's button, not its root;
    • existing Typeahead tests that read the token's text skip the hidden label span.
  • Changeset: a patch [fix].

Evidence

  • The Token, Typeahead and Tokenizer suites pass (335).
  • Reverting either source file fails its new test.

Scope

  • One change: the value token's Tab stop is named and described as the field.
  • Public text contains no internal Meta context.

Writing rubric results

Run over the component:Token delta (FR10, AR2, the Clickable representative state, and the FR2/FR10 verification row):

  • R1 narration: none. Each changed sentence states where a clickable token's naming inputs go now and stays true after this PR. No "now" or "previously".
  • R2 research: none.
  • R3 evidence: none. The verification cells name test suites and states, not a pull request.
  • R4 rejected alternatives: none recorded. Keeping naming on the root was not written into the record.
  • R5 change references: none. A search for # followed by digits, /pull/ and /issues/ finds nothing in the record.
  • R6 open questions: none touched. The record has no open questions.

…and the value (WCAG 2.4.6, 4.1.2)

With a value, Typeahead takes its input out of the Tab order, so the
Tab stop is the token's inner button, and that button was named by the
value alone ("Apple, button"), with no field name, description or
status. It is now labelled by the field label and the value and
described like the input. A clickable Token passes aria-labelledby and
aria-describedby to the inner button that owns its action
(public-component-api INV5) instead of the generic root. Fixes #8945.
@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 31, 2026 1:45pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Preview: the links below open this branch's latest Vercel deployment, which can trail a new push until that push's deployment is ready.

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

Token (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 566B 566B 0B
Lines of Code 516 527 -
Complexity Very High Very High (35) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/BaseTypeahead 56.9KB 56.9KB 0B 10.2KB 10.2KB 0B
core/PowerSearch 171.6KB 171.6KB +84B 20.3KB 20.3KB 0B
core/Token 31.3KB 31.4KB +45B 4.8KB 4.8KB 0B
core/Tokenizer 71.5KB 71.5KB +27B 16.9KB 16.9KB 0B
core/Typeahead 64.3KB 64.3KB +42B 11.6KB 11.6KB 0B
core/TypeaheadItem 6.3KB 6.3KB 0B 3.6KB 3.6KB 0B
lab/DiffViewer 46.9KB 46.9KB +30B 2.1KB 2.1KB 0B
lab/MobilePowerSearch 158.0KB 158.1KB +104B 1.2KB 1.2KB 0B
lab/MobileTokenizer 95.4KB 95.4KB +43B 880B 880B 0B

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 36.0KB 36.0KB 0B

Accessibility Audit

Status: The accessibility audit did not run on this pull request. It runs after merges to main and before each release; pull requests that touch components run it with the ci:heavy-lanes label.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot

astracat-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 31, 2026, 1:53 PM UTC

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The standalone naming works in Chromium, but approval waits on a spec decision: this change sends a clickable Token's aria-labelledby and aria-describedby to its inner button, while Token.spec.md FR10 and AR2 still say ARIA props reach the root and that the inner button is named by its label. An engineering owner needs to either amend those claims in this pull request, recording it by commenting /approve-spec <full head SHA> (the author may do so if they are an engineering owner), or keep root routing, in which case the Typeahead needs another way to name its token.

  • Inside an InputGroup the token is named "Selection Apple" while its combobox is "Favorite fruit Selection"; labelling it with the input's grouped ids at Typeahead.tsx:672, with a grouped test, keeps the group label.
  • The changeset should name the accessibility target (WCAG 2.2 SC 1.3.1 and 2.4.6) and the Token's old behavior.

[Automated review]

size={size}
onClick={handleEnterEditMode}
isDisabled={isDisabled}
aria-labelledby={`${inputGroup ? inputLabelId : fieldLabelId} ${tokenValueId}`}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inside an InputGroup this labels the token with the Typeahead's own hidden label and the value. The combobox it replaces as the Tab stop is labelled by the group label plus that label (getInputARIA), so the grouped Tab stop reads "Selection Apple" where the combobox reads "Favorite fruit Selection". Using the input's ids when grouped keeps the group label:

aria-labelledby={`${inputGroup ? ariaLabelledBy : fieldLabelId} ${tokenValueId}`}

<button
type="button"
onClick={effectiveOnClick}
{...labellingProps}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Token.spec.md FR10 still says supported ARIA props reach the root, and AR2 says the clickable inner button takes its name from its label. This routing needs those claims amended in this pull request, or an owner decision to keep root routing, before it can land.

'@astryxdesign/core': patch
---

[fix] With a value, a `Typeahead`'s Tab stop is the value's token button, and it is now named by the field label and the value ("Fruit Apple", not just "Apple") and described by the field's description and status, so screen reader users know which field it belongs to. A clickable `Token` now passes `aria-labelledby` and `aria-describedby` to its inner button.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

spec:AST-017 FR53 asks a patch accessibility fix's changeset to name the accessibility target it serves (for example WCAG 2.2 SC 1.3.1 and 2.4.6) and the old behavior, which for Token is the props landing on the root span.

Main's #8862 fix names the input with the field label plus a caller
aria-labelledby, and sets Field's labelID only for that case. The value
token's button now takes the same name plus the value (with a test), and
labelID is always set because the token references the field label. Token
tests keep both sides.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a spec decision: a clickable Token now sends aria-labelledby and aria-describedby to its inner button, but Token.spec.md FR10 and AR2 still say ARIA props reach the root and that the inner button is named by its label. An engineering owner needs to either amend those claims in this pull request, recording the decision by commenting /approve-spec <full head SHA> (the author may do so as an engineering owner), or keep root routing, in which case the Typeahead needs another way to name its token. Two fixes are needed either way:

  • A disabled link Token (href with isDisabled and no onRemove) no longer renders a caller's aria-labelledby or aria-describedby, because that branch does not spread labellingProps.
  • The changeset should name the WCAG criteria it serves (for example 2.4.6 and 4.1.2) and Token's old behavior, as spec:AST-017 FR53 asks.

The Typeahead naming itself works: the Tab stop reads "Fruit Apple", and "Favorite fruit Selection Apple" inside an InputGroup.

[Automated review]

Comment on lines +302 to +303
'aria-labelledby': ariaLabelledBy,
'aria-describedby': ariaDescribedBy,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These two props now leave rest, but the disabled link branch (href with isDisabled and no onRemove, lines 373-397) spreads only rest and sharedProps, so a disabled link token renders neither: Chromium names it by its label instead of the caller's aria-labelledby and drops the description. Spreading {...labellingProps} after {...rest} there keeps them on the root, as Token.spec.md FR10 requires, and a Token test over every structure would catch it.

… AR2 say where clickable naming goes; changeset names old behavior and WCAG

Review follow-up: aria-labelledby and aria-describedby left rest, and the
disabled link branch spread only rest, so a disabled link token dropped
them; it now spreads labellingProps on its root, with a test. Token FR10
and AR2 record that a clickable token's naming inputs reach its inner
button (public-component-api INV5), and the FR10 verification row checks
it. The changeset names Token's old routing and WCAG 2.4.6 and 4.1.2.
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval waits on a spec decision: this pull request amends Token.spec.md FR10 and AR2 so a clickable Token sends aria-labelledby and aria-describedby to its inner button, and an engineering owner needs to approve that amendment by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner) or keep root routing. The Typeahead naming itself works: the Tab stop now reads "Fruit Apple" with the field's description, and "Favorite fruit Selection Apple" inside an InputGroup. Two fixes are needed either way:

  • Amend the Clickable representative state in Token.spec.md ("One inner button named by the label") to match the amended AR2.
  • Add a ## Writing rubric results section to the description, which a record change needs before spec approval.

[Automated review]

- **AR2 — Names.** In the clickable and link-and-remove structures the inner
button or link takes its name from the Label. In the link structure the link
button or link takes its name from the Label, or, in the clickable structure,
from a caller `aria-labelledby` (FR10). In the link structure the link

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Clickable representative state at line 148 still says "One inner button named by the label". With this AR2 a caller aria-labelledby names that button instead, so the record now gives two answers; amend line 148 with it, for example "One inner button named by the label, or by a caller aria-labelledby (FR10)".

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 2c48ffb

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
Typeahead tests keep both sides: this branch's token-naming tests and
main's aria-required / aria-invalid tests.
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 8ce2342

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This looks ready to merge: the value token's Tab stop now reads "Fruit Apple" with the field's description and error, including inside an InputGroup and with a caller aria-labelledby, and the Token.spec.md FR10 and AR2 amendment it relies on has engineering-owner approval for this head. The earlier requests, the Clickable representative state and the writing-rubric results, are both addressed, so nothing more is needed from you.

[Automated review]

@cixzhang
cixzhang merged commit b2e79c4 into main Oct 31, 2026
51 checks passed

This branch was successfully deployed

1 active deployment
Preview — 8ce23424 Deployed Oct 31, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Typeahead: with a value, the Tab stop is the token's button, named by the value without the field label

1 participant