What happens
In @cratis/components 4.1.0, keyboard activation of a ColumnFilterMenu trigger opens the portalled filter dialog and focus moves into its value control. Pressing Escape closes the overlay, but document.activeElement becomes BODY instead of the exact filter trigger that opened it.
This path begins with an actual trigger activation; it is not an initially-open dialog with no invoker.
Evidence
The evidence is bound to exact tag v4.1.0 (c4c4cc9a13c6416eb3ab31701546e961b4e9da72):
ColumnFilterMenu.tsx composes the controlled DialogTrigger, AriaButton trigger, portalled Popover, and AriaDialog; Components owns the complete interaction boundary.
- A current consumer browser run records that Escape closes the filter overlay and the active element afterward is
BODY.
- Exact-tag ColumnFilterMenu specs cover labels, stable parts, custom elements, localization, provider messages, and state attributes, but do not dismiss with Escape and assert focus equality with the invoker.
- Package metadata advertises
focus.restore, while CONFORMANCE.md bounds its checks and does not instrument vendor focus traps or dismissal listeners; the observed action/result therefore remains unguarded.
What it costs a consumer
The consumer supplies ordinary column field/header/filter/placeholder props and no custom focus or Escape behavior. Keyboard users lose context after dismissing a filter and must navigate back to the table controls. A consumer requestAnimationFrame focus patch or wrapper ref would duplicate package ownership, depend on dismissal timing, and leave other DataPage consumers exposed.
Suggested fix — the seam
Restore focus inside ColumnFilterMenu's package-owned trigger/composition boundary when Escape dismisses the overlay. Add a package-owned real-browser regression that activates the exact trigger, moves focus to the value control, presses Escape, and retry-asserts focus equality with that trigger, both before and after applying and reopening a constraint.
What is explicitly not being asked for
- No consumer post-close focus patch, timeout, or wrapper trigger ref.
- No prescribed internal mechanism: current evidence does not settle whether controlled state, ref forwarding, React Aria composition, or another detail causes it.
- No redesign of every Dialog or Popover from this one ColumnFilterMenu defect.
- No change to filtering, localization, or match-mode semantics.
Related, not duplicate
#211 is broad cross-browser and assistive-technology certification work. It is related, but it does not describe or settle this atomic ColumnFilterMenu Escape-to-trigger defect.
What happens
In
@cratis/components4.1.0, keyboard activation of a ColumnFilterMenu trigger opens the portalled filter dialog and focus moves into its value control. Pressing Escape closes the overlay, butdocument.activeElementbecomesBODYinstead of the exact filter trigger that opened it.This path begins with an actual trigger activation; it is not an initially-open dialog with no invoker.
Evidence
The evidence is bound to exact tag
v4.1.0(c4c4cc9a13c6416eb3ab31701546e961b4e9da72):ColumnFilterMenu.tsxcomposes the controlledDialogTrigger,AriaButtontrigger, portalledPopover, andAriaDialog; Components owns the complete interaction boundary.BODY.focus.restore, whileCONFORMANCE.mdbounds its checks and does not instrument vendor focus traps or dismissal listeners; the observed action/result therefore remains unguarded.What it costs a consumer
The consumer supplies ordinary column field/header/filter/placeholder props and no custom focus or Escape behavior. Keyboard users lose context after dismissing a filter and must navigate back to the table controls. A consumer
requestAnimationFramefocus patch or wrapper ref would duplicate package ownership, depend on dismissal timing, and leave other DataPage consumers exposed.Suggested fix — the seam
Restore focus inside ColumnFilterMenu's package-owned trigger/composition boundary when Escape dismisses the overlay. Add a package-owned real-browser regression that activates the exact trigger, moves focus to the value control, presses Escape, and retry-asserts focus equality with that trigger, both before and after applying and reopening a constraint.
What is explicitly not being asked for
Related, not duplicate
#211 is broad cross-browser and assistive-technology certification work. It is related, but it does not describe or settle this atomic ColumnFilterMenu Escape-to-trigger defect.