Resizable Panels#
Element Web's left-panel (room list sidebar) uses a resizable, collapsible panel system built on the react-resizable-panels library. It replaced a bespoke Resizer class that had been in matrix-react-sdk since 2018. The migration landed in March 2026 (PR #32742) and the legacy room list code was fully removed in July 2026 (PR #34040).
Architecture#
The resize system is layered across three packages:
| Layer | Package | Key file(s) |
|---|---|---|
| Shared UI components | packages/shared-components | resize/ module |
| App-level view model | apps/web | ResizerViewModel.ts |
| Legacy (removed) | matrix-react-sdk | resizer.ts |
packages/shared-components/src/resize/ module#
Three presentational components wrap react-resizable-panels primitives and accept a vm prop following the ViewModel<Snapshot, Actions> pattern:
GroupViewβ wrapsreact-resizable-panels'Group; extracts the left-panel size from the library'sonLayoutChangedcallback and forwards it to the view model viaonLeftPanelResized(newSize).LeftResizablePanelViewβ wrapsPanel; readsinitialSizeandisCollapsedfrom the view-model snapshot; passesinertwhen collapsed; usesgroupResizeBehavior="preserve-pixel-size".SeparatorViewβ wrapsSeparator; renders a thick "bar" with drag icon when collapsed and a 1 px border when expanded; pointer events delegate to the view model to distinguish click from drag .
The shared snapshot type ResizerViewSnapshot carries two fields: isCollapsed: boolean and initialSize?: number (percentage).
ResizerViewModel (app layer)#
apps/web/src/viewmodels/structures/ResizerViewModel.ts is the single concrete view model. It implements all three action interfaces (SeparatorViewActions, LeftResizablePanelViewActions, GroupViewActions) and owns all state-persistence and collapse logic .
State Persistence#
Panel state is saved to SettingsStore at SettingLevel.DEVICE (device-local, not synced across devices) using two settings :
| Setting key | Type | Purpose |
|---|---|---|
RoomList.panelSize | number (integer %) | Last non-zero panel width |
RoomList.isPanelCollapsed | boolean | Whether the panel was collapsed |
The two settings are kept separate so that the panel can restore to its last non-zero width after an app reload even when it was collapsed at exit .
On startup, getInitialState() checks RoomList.isPanelCollapsed (and AutoCollapse.shouldStartCollapsed()) first; if true it sets initialSize: 0. Otherwise it reads RoomList.panelSize as the starting size.
During resize, onLeftPanelResized() :
- Skips the very first event (fired on initial render) to avoid overwriting stored values.
- Rounds fractional widths to prevent subpixel blur (PR #33052).
- Writes
RoomList.panelSizeonly whennewSize !== 0; always writesRoomList.isPanelCollapsed.
Resize events during a drag are debounced 50 ms via onLeftPanelResize ; the onLayoutChanged-based onLeftPanelResized fires on drag end.
Collapse / Expand#
- Single click on separator β expands to
RoomList.panelSize ?? 100%. - Double click on separator β collapses via
panelHandle.collapse(). - Click vs drag discrimination β a
MouseClickHandlerutility tracksonPointerMovebetweenonPointerDownandonPointerUp; if movement is detected it suppresses the click handler. - Auto-collapse β an
AutoCollapseorchestrator (separate from user interaction) can collapse or expand the panel programmatically, e.g., when a call starts or the window is resized below a threshold (PR #32964).
Room List Section State#
Room list section expansion state (collapsed/expanded per-section) is persisted separately via RoomList.SectionExpansionState (PR #34351, merged 2026-07-29). Without this, sections reset to expanded on every reload (see issue #34317).
Legacy Resizer (Removed)#
The original Resizer class in matrix-react-sdk/src/resizer/resizer.ts attached a mousedown listener to the container element, called event.preventDefault() to suppress native drag behavior , and manually tracked mousemove/mouseup/mouseleave on document.body . It relied on FixedDistributor for flex-based size calculation. The new implementation replaces all of this with react-resizable-panels + pointer events, eliminating several CSS :hover pseudo-class bugs inherent in the preventDefault() approach.