Resizable

A hand-drawn split view whose panels resize by dragging or arrow-keying the divider.

Overview

Resizable is a split view whose panels the user can resize — built on pointer events, no third-party engine. Use it for adjustable layouts: a nav / editor / inspector three-pane, a list-detail split, a preview pane. Lay out ResizablePanels separated by ResizableHandles inside a ResizablePanelGroup; sizes are percentages of the group.

Anatomy

A flex ResizablePanelGroup laid out along its direction, holding ResizablePanels split by ResizableHandles. Each handle is a hand-drawn divider (@ghds/sketch-core line) that is a focusable role="separator" with aria-valuenow / aria-valuemin / aria-valuemax. Dragging a handle — or focusing it and pressing the arrow keys — redistributes space between its two neighbouring panels.

Variants & States

The group’s direction ('horizontal' or 'vertical') is the main axis; the handle can show a grip via withHandle.

State Status Notes
Dragging Implemented Pointer drag on a handle resizes the adjacent panels.
Focus Implemented The handle is a focusable separator; arrow keys resize in 5% steps.
With grip Implemented withHandle renders a visible grip in the middle of the handle.
Disabled Not implemented
Loading Not implemented

Usage

Do

  • Give the ResizablePanelGroup (or a wrapper) an explicit width AND height — it fills its container.
  • Set minSize / maxSize on panels that must not collapse or dominate.
  • Use withHandle on the divider so the drag affordance is visible, especially on touch.

Don't

  • Don't rely on drag alone — the arrow-key path is what makes it keyboard accessible; keep the handle focusable.
  • Don't nest deeply resizable groups without need; the resize model is per-pair of neighbours.

Accessibility

Content

Put a scrollable region inside each panel — panels set overflow: auto, so content that exceeds a shrunken panel scrolls rather than clipping. Keep the most important pane above its minSize.

Props API

React@ghds/react

PropTypeDefaultDescription
direction'horizontal' | 'vertical'Axis panels are laid out and resized along (ResizablePanelGroup). Required.
defaultSizenumberInitial panel size as a percentage of the group (ResizablePanel). Defaults to an equal split.
minSizenumber10Smallest percentage the panel may shrink to (ResizablePanel).
maxSizenumber90Largest percentage the panel may grow to (ResizablePanel).
withHandlebooleanfalseShows a visible grip in the middle of the handle (ResizableHandle).
...restHTMLAttributes<HTMLDivElement>Native <div> attributes pass through to each part.

Web Components@ghds/web-components

PropTypeDefaultDescription
direction'horizontal' | 'vertical''horizontal'Reflected attribute; axis the panels are laid out and resized along (<gh-resizable-group>).
default-sizenumberInitial panel size as a percentage of the group (<gh-resizable-panel>). Defaults to an equal split.
min-sizenumber10Smallest percentage the panel may shrink to (<gh-resizable-panel>).
max-sizenumber90Largest percentage the panel may grow to (<gh-resizable-panel>).
with-handlebooleanfalseReflected attribute; shows a visible grip in the middle of the handle (<gh-resizable-handle>).

React Native@ghds/react-native

PropTypeDefaultDescription
direction'horizontal' | 'vertical'Axis the panels are laid out and resized along (ResizablePanelGroup). Required.
defaultSizenumberInitial panel size as a percentage of the group (ResizablePanel). Defaults to an equal split.
minSizenumber10Smallest percentage the panel may shrink to (ResizablePanel).
maxSizenumber90Largest percentage the panel may grow to (ResizablePanel).
withHandlebooleanfalseShows a visible grip in the middle of the handle (ResizableHandle).
style / testIDStyleProp<ViewStyle> / stringGroup container style, and a test handle. Touch-drag only — there is no arrow-key resize on React Native.

Code examples

// React — @ghds/react
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@ghds/react/resizable';

<div style={{ width: 480, height: 240 }}>
  <ResizablePanelGroup direction="horizontal">
    <ResizablePanel defaultSize={50}>One</ResizablePanel>
    <ResizableHandle withHandle />
    <ResizablePanel defaultSize={50}>Two</ResizablePanel>
  </ResizablePanelGroup>
</div>;
<!-- Web Components — @ghds/web-components -->
<script type="module">
  import '@ghds/web-components/resizable-group';
  import '@ghds/web-components/resizable-panel';
  import '@ghds/web-components/resizable-handle';
</script>
<gh-resizable-group direction="horizontal">
  <gh-resizable-panel default-size="50">One</gh-resizable-panel>
  <gh-resizable-handle with-handle></gh-resizable-handle>
  <gh-resizable-panel default-size="50">Two</gh-resizable-panel>
</gh-resizable-group>
// React Native — @ghds/react-native
// Touch-drag only — there is no arrow-key resize on React Native.
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@ghds/react-native/resizable';

<ResizablePanelGroup direction="horizontal" style={{ height: 240 }}>
  <ResizablePanel defaultSize={50}>{/* one */}</ResizablePanel>
  <ResizableHandle withHandle />
  <ResizablePanel defaultSize={50}>{/* two */}</ResizablePanel>
</ResizablePanelGroup>;

Live Demo

React and Web Components render live below, driven by the same tokens — drag either divider, or focus it and press the arrow keys to resize, and toggle dark mode in the header to see both respond identically. React Native can’t run in a browser without additional react-native-web wiring (not set up in this site), so its usage is shown as a code sample instead. (On React Native the handle is touch-drag only — there is no arrow-key resize.)

React @ghds/react

Web Components @ghds/web-components

One
Two