# Build a Resizable Panels Layout: VS Code and Linear Style

Source: https://www.techinterview.org/post/3233475299/build-resizable-panels-layout-vscode-linear/
Updated: 2026-07-12 · techinterview.org

"Build a resizable panels layout" is the layout cousin of the Build-a-modal question. VS Code, Linear, and Slack all use the pattern: a sidebar you can drag wider, a main area that flexes, and sometimes a third panel that collapses. The interview probes pointer math, persistence, accessibility, and modern CSS knowledge.

## Requirements to clarify

- Two panels or three?

- Horizontal split, vertical split, or both?

- Min and max sizes per panel?

- Persist sizes to localStorage?

- Collapse fully (panel hidden) when dragged below a threshold?

- Keyboard support for the resize handle?

## The CSS foundation

Use CSS Grid with named columns. The handle is its own column with a small fixed width.


```
.layout {
  display: grid;
  grid-template-columns: var(--left) 6px 1fr;
  height: 100vh;
}
```

Drive `--left` from JS state. The handle is just a div in the middle column with `cursor: col-resize`. This avoids absolute positioning and the layout flexes naturally.

## Pointer handling

On `pointerdown` on the handle:

- Capture the pointer (`handle.setPointerCapture(e.pointerId)`) so events keep arriving even if the user drags outside the window

- Record start X and start width

- Set `document.body.style.userSelect = "none"` and `cursor: col-resize` to prevent text selection during the drag

On `pointermove`: compute new width = start width + (current X - start X). Clamp to min/max. Update CSS variable.

On `pointerup`: release pointer capture, restore body styles, persist size to localStorage.

## Why pointer events, not mouse events

Pointer events unify mouse, touch, and pen. `setPointerCapture` is the cleanest way to keep tracking a drag past the window edge. Mousemove on the body works but breaks on iframes and is less robust.

## Min, max, and collapse

Define min and max in pixels or rem. When the user drags below a threshold (e.g., min - 30 px), snap to a fully-collapsed state (width 0, panel hidden) and keep dragging from that "collapsed" anchor. Releasing past the threshold snaps back to min.

## Keyboard accessibility

The handle should be focusable and operable from the keyboard. WAI-ARIA defines the `separator` role for resize handles:

- `role="separator"`

- `aria-orientation="vertical"` (for a horizontal split)

- `aria-valuenow`, `aria-valuemin`, `aria-valuemax` in pixels or %

- Arrow keys move by a fixed step (e.g., 10 px)

- Home/End jump to min/max

- Enter or Space toggles collapse

## Persistence

Save sizes to localStorage on drag end (not on every move — too chatty). Restore on mount; if missing, fall back to a sensible default. Watch out: SSR mismatch if you read localStorage during render. Use a useEffect or a "client-only" guard.

## Performance

- Use `transform` or CSS variables, not `style.width` directly, to avoid layout on every move

- Throttle pointermove with `requestAnimationFrame` if you do expensive work in the handler

- Containment: `contain: layout style` on each panel reduces reflow scope

## Edge cases

- Window resize: clamp panel sizes to new viewport

- Touch devices: 6 px handle is too narrow; expand the hit area with a wider invisible padding

- RTL layouts: flip drag direction

- Inside a flex parent: ensure your grid container has a defined width or grows correctly

## What separates senior from staff

Junior candidates code the drag math. Senior candidates think about the API for nesting (a panel that itself contains a split layout). Staff candidates think about the data model: sizes as a tree of weights, persistence as a serialized layout, and how the user can reset to defaults. Mentioning `react-resizable-panels` by Brian Vaughn (a popular OSS implementation) signals you read modern frontend code.

## Frequently Asked Questions

### Should I use a library?

For interview: implement from scratch. For production: `react-resizable-panels` is excellent and handles persistence, collapse, and keyboard out of the box.

### What about iframe content inside a panel?

Pointer events from outside the iframe don't cross the boundary, so set `pointer-events: none` on the iframe during a drag (and restore after). Otherwise the drag halts the moment the cursor enters the iframe.

### What if I want a 3-panel layout?

Two handles, two CSS variables. Each handle resizes the panel to its left; the rightmost panel takes `1fr` and absorbs the remainder.
