Skip to content

fix(core): make Resizable separator keyboard-operable#3729

Open
bhamodi wants to merge 1 commit into
facebook:mainfrom
bhamodi:a11y/resizable-keyboard
Open

fix(core): make Resizable separator keyboard-operable#3729
bhamodi wants to merge 1 commit into
facebook:mainfrom
bhamodi:a11y/resizable-keyboard

Conversation

@bhamodi

@bhamodi bhamodi commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Keyboard resizing of Resizable panels was dead code. The focusable control is the outer role="separator" div (ResizeHandle.tsx: aria-valuenow/min/max, tabIndex={isDisabled ? -1 : 0}), but onKeyDown={handleKeyDown} was attached to the inner, absolutely-positioned hit-area child. Keydown events fire on the focused element and bubble up -- they never travel down to a descendant -- so the Arrow / Home / End / Enter handler (handleKeyDown, ResizeHandle.tsx:387-442) never ran for keyboard users. Pointer dragging worked; keyboard operation of the WAI-ARIA window-splitter pattern did not.

This moves onKeyDown onto the focusable separator so the handler actually fires. It is placed after the {...props} spread and composed with any consumer-supplied onKeyDown (props.onKeyDown?.(e) then handleKeyDown(e)). Ordering choice: the sibling handlers (onDoubleClick / onFocus / onBlur) sit before {...props}, so a consumer prop can override them. For onKeyDown that would silently re-break keyboard resizing, so this accessibility-critical handler is placed last and composes rather than replaces -- a consumer onKeyDown still runs, and the resize handler can't be clobbered.

The bug shipped because there was no test file for ResizeHandle / useResizable. This PR adds one.

handleKeyDown (unchanged) supports: ArrowRight / ArrowDown grow, ArrowLeft / ArrowUp shrink (step 10, or 50 with Shift), Home jumps to minSizePx, End jumps to maxSizePx (only when a finite max is set), Enter toggles collapse (only when collapsible). Horizontal handles are RTL-aware. Keyboard resizing is pure state math in useResizable (_onResizeStart seeds a ref from size state; _onResizeMove applies a delta), so it needs no layout measurement and its observable effect is aria-valuenow.

Changes

  • Resizable/ResizeHandle.tsx: move onKeyDown from the inner hit-area child to the focusable role="separator" element; place it after {...props} and compose with any consumer onKeyDown. Pointer handlers stay on the hit area.
  • Resizable/ResizeHandle.test.tsx: new file (the gap that let the bug ship).

Test plan

  • node_modules/.bin/vitest run --root . packages/core/src/Resizable -- 11 pass (ResizeHandle.test.tsx). Coverage: ARIA wiring + orientation, separator focusability, ArrowRight/ArrowLeft (± step), Shift = large step, Home → min, End → max, vertical handle ArrowDown/ArrowUp, Enter collapse when collapsible, disabled ignores input (tabIndex -1), and consumer onKeyDown runs alongside resizing (guards the ordering choice).
  • Failing-first confirmed: with the source change stashed (test file kept), the 8 keyboard tests fail (aria-valuenow stays 200) while the 3 non-keyboard tests (ARIA wiring, focusable, disabled) pass -- exactly the signature of a handler that never fires. After the fix, all 11 pass.
  • node_modules/.bin/tsc --project packages/core/tsconfig.json --noEmit -- clean.
  • node_modules/.bin/eslint on both changed files -- clean.

Notes

Found during an a11y audit of core components; scoped to one fix. It deliberately does not change role="separator" emission for non-resizable handles (a separate finding), the keyboard steps, or any pointer logic. The file header already documented "keyboard support," which is now actually true.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

@bhamodi is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 9, 2026
@bhamodi bhamodi force-pushed the a11y/resizable-keyboard branch from 1d33432 to 31b1576 Compare July 9, 2026 07:34
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.

1 participant