Skip to content

fix(Item, Token): a control inside rings itself, not the row or token too - #8869

Open
cixzhang wants to merge 7 commits into
mainfrom
fix/focus-ring-one-owner-item-token
Open

cixzhang wants to merge 7 commits into
mainfrom
fix/focus-ring-one-owner-item-token

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Part 1 of 3 for #8850: Item and Token. ClickableCard and SelectableCard follow in part 2, Thumbnail and Selector in part 3. ListItem gets its own follow-up after #8803.

User impact

A keyboard user who tabs to an action inside a row (a Share button in an Item's endContent) or to a Token's remove button sees two focus rings: the control's own, and a second around the whole row or token. That makes it unclear which one has focus. A row with no action of its own rang too. Now each tab stop shows one ring.

Share focused in "With End Actions" Before After
1280
390
Remove focused in "Clickable With Remove" Before After
1280
390

Problem and solution fit

  • Cause: Item and Token paint their ring with focusOutlineStyles.focusWithin, which compiles to :has(:focus-visible). That matches any focused descendant, not only the control the wrapper paints for.
  • Fix: the private focus-owner attribute gets its one owner module, privateAttributes/focusOwner.stylex.ts (architecture:public-component-api INV23). It writes the attribute and its selector once and exports only the shared wrapper ring focusWithinOwner and the props that mark the control (focusOwnerProps, focusOwnerGroupProps). A wrapper rings only while its marked control, a direct child or one level down inside a marked group, has keyboard focus. No package entry exports the module.
  • Item: the invisible button and link carry the marker. In a numbered List they sit inside the content cell beside the marker; that cell spreads focusOwnerGroupProps, so a numbered row keeps its ring. Delegation mode (interactiveRef) keeps focusWithin, because its nested control is the row's sole tab stop by contract.
  • Token: the invisible button and the link (with or without a remove button) carry the marker. The remove button rings itself, as it already did.

Current specification

architecture:interaction-modality INV3 (current): "One focus move has one owning indicator … sibling controls paint their own focus". Its failure signal lists "two indicators".

Evidence

  • FocusRingOwner.a11y.chromium.spec.ts (real Chromium, 1280 and 390 px): tabs through every stop and asserts exactly one painted outline at each. On main it fails with two rings while Share or Remove is focused. It uses a new shared helper, __tests__/focusRings.ts (ringedElements), which lane 4 can reuse for [Bug] Seven tab stops draw the browser's default focus ring instead of the theme's (Table, CodeBlock, Carousel, ChatMessageList, Tooltip, HoverCard) #8852.
  • Compiled CSS: focusOwner.source-build.test.mjs compiles the owner module and checks every ring rule sits on the wrapper, keyed to the marked control's :focus-visible, never :has(...) * on every descendant.
  • Token inside an Item: a Chromium case tabs Item › With Token Action and asserts one ring at each stop.
  • Numbered rows: the same spec tabs Item › Numbered With Actions (a button row and a link row in <List listStyle="decimal">) and asserts one ring at each.
  • New stories: Item › With End Actions, Item › Numbered With Actions (a row with an action and a Share button, plus a row with no action and a Restore button) and Token › Clickable With Remove.
  • Unit: Item, Token, List and utils suites pass (946).

Scope

Changeset: [fix] patch for @astryxdesign/core.

LOC

+421 / −13: about 70 lines in the components, the owner module and the lint rule, 80 in stories, about 190 in the Chromium spec, its ring helper and the compiled-CSS test, plus the changeset.

Merge gate

Merges after the swizzle copy change, which makes astryx swizzle Item and astryx swizzle Token copy the owner module into the ejected output.

… too

architecture:interaction-modality INV3: one focus move has one owning
indicator. Item and Token drew their ring with focusWithin
(:has(:focus-visible)), which matches any focused descendant, so a control
in an Item's start or end slot, or a Token's remove button, drew its own
ring and the row or token drew a second around it; a row with no action of
its own rang too.

A new shared style, focusWithinOwner, rings a wrapper only for the direct
child marked data-astryx-focus-owner (FOCUS_OWNER_ATTRIBUTE). Item marks
its invisible button and link (delegation mode keeps focusWithin: the
nested control is the row's sole tab stop by contract); Token marks its
button and link. Other controls ring themselves.

New stories (Item With End Actions, Token Clickable With Remove) and
FocusRingOwner.a11y.chromium.spec.ts: one ring at every tab stop at 1280
and 390 px; it fails on main (two rings while Share or Remove is focused).
focusRings.ts is a shared helper for such specs.
@vercel

vercel Bot commented Oct 28, 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 7:17pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 1, 2026, 3:42 AM 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 focus-ring fix works: each tab stop in the new Item and Token stories now paints one outline, as component:Token AR4 and architecture:interaction-modality INV3 require. Approval waits on one decision: focusWithinOwner and FOCUS_OWNER_ATTRIBUTE are reachable from the published @astryxdesign/core/utils and @astryxdesign/core/utils/focusOutline.stylex entry points, which makes them public API under architecture:public-component-api INV12 even though the description calls them internal, and no current spec admits them, so an engineering owner either admits them by commenting /approve-spec <full head SHA> on this pull request (the author may do so as an engineering owner) or asks for the owner condition to stay component-local in Item and Token so nothing new is exported. Independently, please fix two displaced doc comments:

  • apps/storybook/stories/Item.stories.tsx: /** Disabled items. */ now opens the With End Actions description and leaves Disabled with none; move it back above export const Disabled.
  • packages/core/src/utils/focusOutline.stylex.ts: FOCUS_OWNER_ATTRIBUTE sits between FOCUS_OUTLINE_PARTS and its JSDoc, so that export loses its documentation; declare the constant above that block or after FOCUS_OUTLINE_PARTS_NONE.

[Automated review]

};

/** Disabled items. */
/**

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 block landed under /** Disabled items. */ (line 254), so Storybook now gives With End Actions the description "Disabled items." followed by this text, and Disabled (line 292) has none. Move /** Disabled items. */ down to sit directly above export const Disabled.

* ```
*/
/** Marks the direct child a `focusWithinOwner` wrapper rings for. */
export const FOCUS_OWNER_ATTRIBUTE = 'data-astryx-focus-owner';

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 sits between the JSDoc at lines 61-84 and FOCUS_OUTLINE_PARTS (line 88), so FOCUS_OUTLINE_PARTS loses its documentation, including the warning that it is NOT usable inside another file's stylex.create. Declare the constant above that block or after FOCUS_OUTLINE_PARTS_NONE.

* so a nested component's own owner never rings this wrapper. Pair it
* with {@link focusOutlineStyles.suppressed} on the owner.
*/
focusWithinOwner: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

focusWithinOwner is reachable through focusOutlineStyles and focusOutlineProps from @astryxdesign/core/utils, and with FOCUS_OWNER_ATTRIBUTE from @astryxdesign/core/utils/focusOutline.stylex. Under architecture:public-component-api INV12 that makes them public API despite the internal label, so an engineering owner needs to admit them; otherwise the owner condition can stay component-local in Item and Token, composing the focusVars tokens, so nothing new is exported.

'@astryxdesign/core': patch
---

[fix] Focusing a control inside an `Item` (an action in `startContent` or `endContent`) or a `Token`'s remove button no longer also rings the whole row or token: each tab stop shows one focus ring. A row or token rings only for its own action, and a row without one draws no ring.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: delegation rows (CheckboxListItem, RadioListItem, any Item with interactiveRef) keep the any-descendant ring, so an endContent control there still shows the row's ring too. Consider scoping this sentence to rows and tokens that have their own button or link.

@cixzhang
cixzhang marked this pull request as draft October 30, 2026 21:05
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 30, 2026
…abled story's doc comment

CodeBunny: focusWithinOwner and FOCUS_OWNER_ATTRIBUTE were reachable from
the published @astryxdesign/core/utils and utils/focusOutline.stylex entry
points, which would make them public API (public-component-api INV12).
They move to src/internal/focusOwner.ts, which no entry point exports, so
nothing new is published and focusOutline.stylex.ts is unchanged from main.
Item.stories: '/** Disabled items. */' goes back above Disabled.
….stylex.ts

The repo's focus-outline-shared lint rule allows rings only in a
focusOutline.stylex file; the internal file keeps that rule and the shared
tokens, and no package entry point exports it.
@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

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

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/BreadcrumbItem 69.6KB 69.7KB +125B 11.4KB 11.5KB +70B
core/CheckboxListItem 57.1KB 57.1KB +74B 11.0KB 11.0KB +71B
core/ContextMenu 77.4KB 77.5KB +111B 15.2KB 15.3KB +78B
core/ContextMenuItem 55.3KB 55.3KB 0B 9.9KB 9.9KB 0B
core/DropdownMenu 75.3KB 75.5KB +135B 15.4KB 15.4KB +77B
core/DropdownMenuCheckboxItem 17.6KB 17.7KB +134B 5.9KB 6.0KB +63B
core/DropdownMenuItem 16.8KB 17.0KB +130B 5.5KB 5.6KB +65B
core/DropdownMenuRadioItem 17.6KB 17.8KB +132B 5.9KB 6.0KB +63B
core/DropdownMenuSubMenu 60.9KB 61.0KB +150B 9.9KB 9.9KB +68B
core/Item 12.7KB 12.8KB +134B 4.9KB 4.9KB +65B
core/ListItem 56.5KB 56.6KB +68B 11.0KB 11.0KB +71B
core/Markdown 147.7KB 147.8KB +87B 18.2KB 18.2KB +85B
core/MoreMenu 75.6KB 75.8KB +140B 15.4KB 15.4KB +77B
core/Pagination 87.9KB 88.1KB +112B 15.7KB 15.8KB +75B
core/PowerSearch 171.7KB 171.9KB +136B 20.3KB 20.4KB +85B
core/RadioListItem 55.0KB 55.1KB +81B 10.0KB 10.1KB +76B
core/Selector 79.5KB 79.6KB +123B 15.4KB 15.5KB +90B
core/SelectorOption 15.9KB 16.1KB +133B 5.4KB 5.5KB +63B
core/Table 87.2KB 87.3KB +114B 15.9KB 16.0KB +76B
core/TableCell 78.6KB 78.7KB +137B 15.7KB 15.7KB +74B
core/TableHeaderCell 78.2KB 78.3KB +121B 15.4KB 15.5KB +74B
core/Token 31.4KB 31.4KB +75B 4.8KB 4.8KB +61B
core/Tokenizer 71.6KB 71.7KB +110B 16.9KB 16.9KB +69B
core/Typeahead 64.4KB 64.5KB +115B 11.6KB 11.7KB +63B
lab/CommentThread 220.2KB 220.3KB +120B 2.4KB 2.4KB 0B
lab/Comments 224.8KB 224.9KB +114B 2.5KB 2.5KB 0B
lab/DiffViewer 46.9KB 47.0KB +133B 2.1KB 2.1KB 0B
lab/MobilePowerSearch 158.2KB 158.4KB +140B 1.2KB 1.2KB 0B
lab/MobileTokenizer 95.5KB 95.6KB +102B 880B 880B 0B
lab/TransferList 69.9KB 70.0KB +99B 1.7KB 1.7KB 0B
lab/TransferListSelector 75.6KB 75.7KB +95B 1.9KB 1.9KB 0B
richtext/RichTextEditorToolbar 215.9KB 216.0KB +120B 454B 454B 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.1KB +76B

Published Package Size

Package Download before Download after Delta Unpacked before Unpacked after Delta Files
@astryxdesign/core 6.52MB 6.52MB +2.2KB (+0.0%) 27.71MB 27.72MB +9.8KB (+0.0%) 3699 → 3703

10 other published packages are unchanged.

Download is the gzip tarball npm fetches; unpacked is what lands in node_modules.

Accessibility Audit

Status: Accessibility did not run on this pull request; add ci:run-a11y to run it. It runs after merges to main and before each release.


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

cixzhang added a commit that referenced this pull request Oct 30, 2026
Follows #8869: focusWithinOwner and FOCUS_OWNER_ATTRIBUTE now come from the
unexported internal/focusOutline.stylex.ts, so nothing new is published.
cixzhang added a commit that referenced this pull request Oct 30, 2026
… ring

Follows #8869: focusWithinOwner and FOCUS_OWNER_ATTRIBUTE come from the
unexported internal/focusOutline.stylex.ts (focusOwnerStyles for the two
selectors' stylex.props lists), so nothing new is published.

@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.

Requesting changes: flat Item rows and both token structures now show one focus ring per tab stop, but two regressions need fixing first.

  • In a numbered list (<List listStyle="decimal">), a ListItem with onClick or href now shows no focus indicator at all, because Item wraps the marked button in its marker-column span and :has(> [data-astryx-focus-owner]:focus-visible) only matches a direct child, so please let the condition reach it and add a numbered row to FocusRingOwner.a11y.chromium.spec.ts.
  • astryx swizzle Item and astryx swizzle Token now emit an import from @astryxdesign/core/internal/focusOutline.stylex, which packages/core/package.json does not export, so please keep the owner condition in each component's own directory or move it to an exported utility that an engineering owner admits.

[Automated review]

focusWithinOwner: {
outlineWidth: {
default: '0',
':has(> [data-astryx-focus-owner]:focus-visible)':

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In a numbered list (<List listStyle="decimal">), Item wraps its content in the markerContent span (packages/core/src/Item/Item.tsx:936-943), so the marked button or link is a grandchild of the <li> and this direct-child condition never matches. The button and link suppress their own outline, so a clickable or link numbered row shows no focus indicator at all (0 painted outlines in Chromium, 1 on the base). Let the condition reach the owner through that wrapper, for example :has(> [data-astryx-focus-owner]:focus-visible, > * > [data-astryx-focus-owner]:focus-visible), and add a numbered row to the Chromium spec.

Comment thread packages/core/src/Item/Item.tsx Outdated
import {
FOCUS_OWNER_ATTRIBUTE,
focusWithinOwnerProps,
} from '../internal/focusOutline.stylex';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

astryx swizzle rewrites this import to @astryxdesign/core/internal/focusOutline.stylex (packages/cli/api/swizzle/copy/copy.mjs:71-73), which packages/core/package.json does not export, so a swizzled Item, and a swizzled Token (Token.tsx:43), fails with ERR_PACKAGE_PATH_NOT_EXPORTED. An ./internal/* entry would not help consumer copies, since those subpaths are reserved for Core and Lab, so keep the owner condition in the component's own directory or move it to an exported utility that an engineering owner admits.

@cixzhang
cixzhang marked this pull request as ready for review October 31, 2026 03:07
…and Token resolve the owner module

CodeBunny on #8869:
- In a numbered List, Item wraps a row's button or link in the content cell
  beside the marker, so the owner condition's direct-child :has() missed it
  and the row showed no ring. The cell now carries
  data-astryx-focus-owner-group, and the condition also matches an owner one
  level down inside such a group. A numbered story and a Chromium case
  check one ring for a button row and a link row.
- Swizzled Item and Token import internal/focusOutline.stylex, which Core did
  not export. It is now a ./internal/* entry like internal/touch: reachable
  for Core, Lab and swizzled copies, outside the compatibility promise
  (architecture:public-component-api INV12).

@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.

Requesting changes: Item rows (flat and numbered) and both token structures now show one focus ring per tab stop, but two things need fixing first.

  • astryx swizzle Item and astryx swizzle Token now emit an import of @astryxdesign/core/internal/focusOutline.stylex, and ./internal/* subpaths are reserved for Astryx's own packages and sit outside the compatibility promise, so please define the owner condition in each component's own styles from focusVars and drop the new export, or have an engineering owner admit it as a public utility.
  • The lint check fails on packages/core/src/internal/focusOutline.stylex.ts:59 (@typescript-eslint/no-unnecessary-type-assertion); dropping the as never on styles.focusWithinOwner fixes it.

The inline comments cover three smaller fixes: the module's stale header, the browser spec's @pr-a11y scope line, and the changeset's wording about delegation rows.

[Automated review]

Comment thread packages/core/package.json Outdated
"types": "./dist/internal/tokenizer-presentation.d.ts",
"default": "./dist/internal/tokenizer-presentation.js"
},
"./internal/focusOutline.stylex": {

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 entry exists so a swizzled Item or Token resolves @astryxdesign/core/internal/focusOutline.stylex, but ./internal/* subpaths are reserved for Astryx's own packages, outside the compatibility promise and limited by lint to Core and Lab (AST-017 FR21). An ejected copy is frozen at one version, so a later patch that changes focusWithinOwnerProps would break its build without a release signal. Defining the owner condition in Item's and Token's own stylex.create from focusVars (already public through theme/tokens.stylex) would let swizzle copy it and make this entry, and the sync-exports.js line, unnecessary.


/** The owner ring, with caller styles last (as focusOutlineProps does). */
export function focusWithinOwnerProps(...extra: unknown[]) {
return stylex.props(styles.focusWithinOwner as never, ...(extra as never[]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CI's lint job fails here: @typescript-eslint/no-unnecessary-type-assertion on styles.focusWithinOwner as never (an error in strict mode, which CI enables). Dropping that as never clears it.

Comment on lines +6 to +11
* @output FOCUS_OWNER_ATTRIBUTE and focusOwnerProps, the ring a wrapper paints
* only for its marked primary control
* @position The internal half of the focus outline utility: same tokens as
* utils/focusOutline.stylex, but no published entry point exports it, so the
* owner marker and its ring stay a detail of Item and Token
* (`architecture:public-component-api` INV12)

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 header names focusOwnerProps (the export is focusWithinOwnerProps), leaves out FOCUS_OWNER_GROUP_ATTRIBUTE, and says no published entry point exports the module, which the new package.json entry makes untrue. utils/focusOutline.stylex.ts:14-16 also still says every ring except Switch's is drawn there.

@@ -0,0 +1,90 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// @pr-a11y scope ^packages/core/src/Item/ ^packages/core/src/Token/ ^packages/core/src/utils/focusOutline ^apps/storybook/stories/(Item|Token)

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 routing line still points at ^packages/core/src/utils/focusOutline; the condition this spec proves lives in packages/core/src/internal/focusOutline.stylex.ts, so a pull request that changes only that file will not run it. Adding ^packages/core/src/internal/focusOutline fixes that.

'@astryxdesign/core': patch
---

[fix] Focusing a control inside an `Item` (an action in `startContent` or `endContent`) or a `Token`'s remove button no longer also rings the whole row or token: each tab stop shows one focus ring. A row or token rings only for its own action, and a row without one draws no ring.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Delegation rows (CheckboxListItem, RadioListItem, any Item with interactiveRef) keep the any-descendant ring, so an end control there still shows the row's outline too (2 outlines in Chromium). Consider scoping this sentence to rows and tokens that have their own button or link.

@cixzhang
cixzhang marked this pull request as draft October 31, 2026 05:06
…, privateAttributes/focusOwner.stylex.ts

Per architecture:interaction-modality INV3 and public-component-api INV23:
the private data-astryx-focus-owner attribute, its group attribute and their
selector are written once, in privateAttributes/focusOwner.stylex.ts, which
exports only the shared focusWithinOwner wrapper ring and the props that set
the attributes. Item and Token spread those exports and never write the
attribute; TokenLink takes them through its link props without typing the
name. No package entry exports the module: the ./internal/focusOutline.stylex
entry and its export-sync line are gone. focus-outline-shared allows the
owner module to write its ring.

A source-build test checks the compiled ring sits on the wrapper, keyed to
the marked control's :focus-visible, never :has(...) * on every descendant;
a Chromium case checks a Token inside an Item shows one ring at each stop.
…ner-item-token

# Conflicts:
#	packages/core/src/Item/Item.tsx
#	packages/core/src/Token/Token.tsx
#	packages/core/src/Token/TokenLink.tsx

@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 fix works where it applies: in the new stories, each tab stop in an Item row with its own action, a Token nested in a row, and a clickable or link Token with Remove now paints one focus ring instead of two. Approval waits on an engineering owner deciding whether packages/core/src/privateAttributes/focusOwner.stylex.ts and its private-attribute convention become the owner of the wrapper focus ring, which #9182, #9183 and #9190 propose but no current record establishes; an engineering owner records that decision by approving those records or by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Two fixes are needed either way:

  • astryx swizzle Item and astryx swizzle Token now emit an import of @astryxdesign/core/privateAttributes/focusOwner.stylex, which Core does not export, so the swizzle copy change has to land in or before this pull request.
  • The changeset says no control inside an Item rings the row any more, but delegation rows (CheckboxListItem, RadioListItem, any Item with interactiveRef) still show two rings; scope it to rows with their own onClick or href.

[Automated review]

'@astryxdesign/core': patch
---

[fix] Focusing a control inside an `Item` (an action in `startContent` or `endContent`) or a `Token`'s remove button no longer also rings the whole row or token: each tab stop shows one focus ring. A row or token rings only for its own action, and a row without one draws no ring.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Delegation rows (CheckboxListItem, RadioListItem, any Item with interactiveRef) keep the any-descendant ring (packages/core/src/Item/Item.tsx:924), so a button in their endContent still shows the row's ring beside its own. Please scope this to rows with their own onClick or href, for example: "Focusing a control in the start or end slot of an Item with its own onClick or href, or a Token's remove button, no longer also rings the whole row or token."

focusOwnerGroupProps,
focusOwnerProps,
focusWithinOwner,
} from '../privateAttributes/focusOwner.stylex';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

astryx swizzle rewrites this import to @astryxdesign/core/privateAttributes/focusOwner.stylex (packages/cli/api/swizzle/copy/copy.mjs:71-73), which packages/core/package.json does not export, so a swizzled Item, and a swizzled Token (Token.tsx:43), no longer resolves its imports until the swizzle copy change lands.

@@ -0,0 +1,100 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// @pr-a11y scope ^packages/core/src/Item/ ^packages/core/src/Token/ ^packages/core/src/utils/focusOutline ^apps/storybook/stories/(Item|Token)

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 routing line names ^packages/core/src/utils/focusOutline, which this pull request leaves unchanged, but not the module the spec proves; adding ^packages/core/src/privateAttributes/focusOwner makes a change to that module alone run this spec.

This branch was successfully deployed

1 active deployment
Preview — cf7501ff 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant