Summary
In the React Aria base, button (and toggle/tabs) focus rings are styled with the CSS :focus-visible heuristic (Tailwind focus-visible: variants) instead of React Aria Components' data-focus-visible attribute. Because React Aria restores focus programmatically when a Menu/Popover/Dialog closes (FocusScope restore), and browsers treat script-initiated element.focus() as focus-visible-worthy, mouse users see the keyboard focus ring on the trigger after every pointer-driven menu interaction.
Reproduction
npx shadcn create → aria base (any style; observed on rhea)
- Add a
MenuTrigger (Button + Menu)
- With a mouse: click the trigger, click a menu item → menu closes
- The trigger renders with the full keyboard focus ring
Where the styling lives today
The aria-base component templates (e.g. apps/v4/registry/bases/aria/ui/button.tsx) no longer carry focus classes — they apply cn-* utility classes, and the focus styling lives in the shared per-style CSS. In apps/v4/registry/styles/style-rhea.css (same pattern in the other styles):
/* MARK: Button */
.cn-button {
@apply focus-visible:border-ring focus-visible:ring-ring/30 ... focus-visible:ring-3 ...;
}
The inconsistency
The same style files already handle this correctly for other aria primitives — the aria twins for checkbox/radio/switch use the data attribute:
@apply ... data-focus-visible:border-ring data-focus-visible:ring-ring/30 data-focus-visible:ring-3 ...;
So the distinction is already established in the codebase; button/toggle/tabs (the components typically used as popover/menu triggers — i.e., exactly the ones hit by focus restore) just didn't get the same treatment.
Suggested fix
For classes consumed by the aria base, style focus rings off data-focus-visible: (RAC sets the attribute from its own interaction-modality tracking, which doesn't misfire on programmatic focus restore) rather than focus-visible:.
Verified locally after swapping focus-visible: → data-focus-visible: on our vendored aria Button/Toggle/ToggleGroup/Tabs: pointer-driven menu flows show no ring; Tab and screen-reader virtual focus still show it.
Builder AI agent posting on behalf of @heatherhelen — human-reviewed before submission.
Summary
In the React Aria base, button (and toggle/tabs) focus rings are styled with the CSS
:focus-visibleheuristic (Tailwindfocus-visible:variants) instead of React Aria Components'data-focus-visibleattribute. Because React Aria restores focus programmatically when a Menu/Popover/Dialog closes (FocusScope restore), and browsers treat script-initiatedelement.focus()as focus-visible-worthy, mouse users see the keyboard focus ring on the trigger after every pointer-driven menu interaction.Reproduction
npx shadcn create→ aria base (any style; observed on rhea)MenuTrigger(Button + Menu)Where the styling lives today
The aria-base component templates (e.g.
apps/v4/registry/bases/aria/ui/button.tsx) no longer carry focus classes — they applycn-*utility classes, and the focus styling lives in the shared per-style CSS. Inapps/v4/registry/styles/style-rhea.css(same pattern in the other styles):The inconsistency
The same style files already handle this correctly for other aria primitives — the aria twins for checkbox/radio/switch use the data attribute:
So the distinction is already established in the codebase; button/toggle/tabs (the components typically used as popover/menu triggers — i.e., exactly the ones hit by focus restore) just didn't get the same treatment.
Suggested fix
For classes consumed by the aria base, style focus rings off
data-focus-visible:(RAC sets the attribute from its own interaction-modality tracking, which doesn't misfire on programmatic focus restore) rather thanfocus-visible:.Verified locally after swapping
focus-visible:→data-focus-visible:on our vendored aria Button/Toggle/ToggleGroup/Tabs: pointer-driven menu flows show no ring; Tab and screen-reader virtual focus still show it.Builder AI agent posting on behalf of @heatherhelen — human-reviewed before submission.