Resize Handle Interaction#
The resize handle system in Element Web (matrix-react-sdk) is split into two layers: a thin presentational React component (ResizeHandle.tsx) that renders the DOM, and a separate Resizer class that drives all interaction logic. Understanding where each concern lives is essential when debugging hover, focus, or drag behavior.
Component and Resizer Architecture#
ResizeHandle.tsx is a stateless component. It emits no events and has no keyboard handlers, ARIA attributes, or tabIndex — it purely applies CSS class names (mx_ResizeHandle, mx_ResizeHandle--horizontal, mx_ResizeHandle--vertical, mx_ResizeHandle_reverse) based on props .
All interaction is delegated to src/resizer/resizer.ts. The Resizer class attaches a mousedown listener to the container (not the handle element itself) . On a matching left-button click:
event.preventDefault()is called to suppress the browser's native drag/text-selection behavior .- A
resizer-resizingclass is added to the container . mousemoveandmouseuplisteners are added todocument.body;mouseleaveis added todocumentto cancel the drag if the cursor leaves the window .
CSS Pseudo-Class Behavior#
Base cursor and layout styles live in _ResizeHandle.pcss — no :hover, :focus, or :active styles are defined there . Visual hover feedback is added at the usage site via ::before pseudo-elements:
| Context | Selector | Source |
|---|---|---|
| MatrixChat left panel | .mx_MatrixChat > .mx_ResizeHandle--horizontal:hover::before | |
| Right panel (MainSplit) | .mx_MainSplit > .mx_RightPanel_ResizeWrapper:hover .mx_ResizeHandle--horizontal::before | |
| Apps drawer | &:hover .mx_ResizeHandle--horizontal::before on container |
Each inserts a 4 px wide × 64 px tall rounded bar at 0.8 opacity using $primary-content colour.
No :focus or :active CSS rules are defined for mx_ResizeHandle anywhere in the codebase — the handle is not focusable (no tabIndex) and carries no focus styling.
Known Pseudo-Class Conflict Issues#
Two confirmed bugs (Firefox 152, reported 2026-07-28) illustrate how the pure-CSS :hover approach interacts badly with pointer capture:
-
Hover stuck after cursor leaves window (element-web#34431): When the cursor exits the browser window while still over the handle, the browser's CSS
:hoverstate is not cleared because themouseleaveevent fires ondocument(which is used to cancel the drag), but the element's:hoverpseudo-class is only cleared when the cursor actually moves off the element — which never happens if the window loses pointer capture first. -
Hover lost after click (element-web#34432): After clicking the resizer, the
:hoverstyle no longer re-applies on subsequent hover. The root cause is linked toevent.preventDefault()inonMouseDown: callingpreventDefault()onmousedownsuppresses the default focus-management step that the browser performs, which in some browsers causes the element to enter an internal state where the:hoverpseudo-class is not re-evaluated until focus moves elsewhere.
Newer Separator Implementation (element-web)#
A parallel, more modern implementation exists in element-web for the room list separator (distinct from the legacy Resizer class). Key design decisions made to avoid the pseudo-class conflicts above:
- Pointer events replace mouse events:
onPointerDown/onPointerMove/onPointerUpare used to disambiguate click from drag via aMouseClickHandlerutility, without callingpreventDefault()on the initiating event . - CSS
:focus-visiblereplaces JS focus tracking: Thewhat-inputlibrary andonFocus/onBlurhandlers were removed . The separator now uses:focus-visiblenatively, animating a 1 px border to 2 px on keyboard focus. display: noneprevents spurious hover: Where the separator should be non-interactive, it is hidden withdisplay: nonerather thanopacity: 0, so the browser cannot match:hoveragainst a visually hidden hit target .- Hit region sizing: The absolutely positioned hit container was reduced from 20 px to 10 px wide to prevent the hover overlay from capturing events intended for an adjacent scrollbar .
Key Files#
| File | Purpose |
|---|---|
src/components/views/elements/ResizeHandle.tsx | Presentational DOM for legacy handles |
src/resizer/resizer.ts | Mouse event wiring, preventDefault(), drag lifecycle |
res/css/views/elements/_ResizeHandle.pcss | Base cursor/layout styles (no pseudo-class rules) |
res/css/structures/_MatrixChat.pcss | :hover visual indicator for left-panel resizer |