Skip to content

aria base: focus rings bind to CSS :focus-visible — mouse users see keyboard rings after every menu/popover close #11258

Description

@heatherhelen

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

  1. npx shadcn create → aria base (any style; observed on rhea)
  2. Add a MenuTrigger (Button + Menu)
  3. With a mouse: click the trigger, click a menu item → menu closes
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions