Resizable Panel Group

Adjacent panels whose sizes people change by dragging or using the keyboard on the handles between them.

<tp-resizable-panel-group>
Resizable Panel Group
Default density Compact density Restart from the initial state Show the copyable source
Inbox Assigned to me Mentions Archive
Q3 planning notes Priya Natarajan · Oct 8 Draft goals for the self-serve billing launch and the onboarding refresh.
Write a reply…

Usage

One group owns the sizes of its panels. Panels and handles must be direct children in order, and every pointer, keyboard and programmatic change goes through the same constraint solver. Give the group a block size; panels share its space along orientation. Nest another group inside a panel for the second axis, as above.

The resize handle is independent of the decorative tp-separator component. Ordinary direct-child elements also serve as panels; omitted handles are generated between adjacent panels and use the same implementation.

Sizes and limits

default-size, min-size and max-size take percentages, pixels or other lengths; a unitless value is a percentage. Double-clicking a handle restores its panel’s default size.

The target selects the panel described by accessible values, keyboard commands and reset. Pointer dragging follows the physical boundary. An enabled handle is a focusable separator with native orientation, values and a relationship to the controlled panel. Its separator orientation is perpendicular to the group axis.

Axis arrows adjust by keyboardStep; Home/End reach allowed extremes; Enter toggles a collapsible target. F6 and Shift+F6 cycle enabled handles.

Bounds
Default density Compact density Restart from the initial state Show the copyable source
Files
20–45%
Editor
At least 16rem

Vertical

orientation="vertical" stacks the panels; the handles then move with Up and Down.

Vertical
Default density Compact density Restart from the initial state Show the copyable source
query.sql
1,284 rows · 42 ms

Collapsible panels

A collapsible panel snaps to collapsed-size when dragged below its minimum, and Enter on its handle toggles it. collapse(), expand() and isCollapsed() drive it from your own controls.

Expansion restores the most recent valid expanded size, then the default or minimum.

Collapsible sidebar
Default density Compact density Restart from the initial state Show the copyable source
Toggle sidebar
Channels
#design-reviews

Layout changes

tp-layout-change reports each committed layout as panel IDs and percentages, and tp-layout-change-complete fires once when a drag settles. Save layouts from the settled event, keyed by stable panel IDs.

Stable authored panel IDs are required when saving a layout across page loads. Missing IDs are generated for the current document lifetime.

Layout events
Default density Compact density Restart from the initial state Show the copyable source
Preview
Inspector
Drag the handle

Controlled sizes

Set sizes to own the layout, in panel order, and accept each proposal in onSizesChange by assigning event.detail.value back; ignoring it keeps the previous layout. Strings are percentages and numbers are pixels. Like the persistence adapter, sizes must be set before the group connects.

Layout presets
Default density Compact density Restart from the initial state Show the copyable source
Side by side Focus on code
checkout.tsx
Preview

Saved layout

Give the group a persistence-key and a persistenceAdapter with load(key) and save(key, layout), and it restores the saved layout on connect and saves each settled one. The library has no storage policy of its own, and the adapter must be in place before the group connects.

Persisted sizes
Default density Compact density Restart from the initial state Show the copyable source
Folders
Messages
Drag the handle, then press Refresh or reload the page.

Disabled

disabled on the group freezes every handle; on a panel, it keeps that panel’s pixel size while the others adjust.

Disabled
Default density Compact density Restart from the initial state Show the copyable source
Locked layout
Unlock editing to resize

Standalone separator

ResizeSeparatorController is the Handle’s separator behavior for one extent that its owner controls outside a group, such as a table column. The owner supplies the current value, its limits and the key steps, and applies the proposals it receives:

import { ResizeSeparatorController, resizeSeparatorTargetCss } from '@tweakpad/ui'; const separator = new ResizeSeparatorController({ host: element, axis: () => 'horizontal', direction: () => 'ltr', value: () => width, min: () => 48, max: () => 600, steps: () => ({ step: 8, shift: 32, alt: 1 }), propose: ({ value, phase }) => { if (phase === 'settled') width = value; // `live` previews a drag, `cancelled` restores }, }); separator.connect(line); for (const [name, value] of Object.entries(separator.attributes())) line.setAttribute(name, value);

The arrow pointing to the end of the axis grows the extent and the opposite arrow shrinks it, mirrored in right-to-left content; Shift and Alt use their own steps, and Home and End reach the limits. A drag captures the pointer, blocks text selection and shows the resize cursor; Escape and pointer cancellation restore the start value. resizeSeparatorTargetCss(selector, axis) returns the structural rule of the enlarged pointer target, which reads --tp-resize-target-fine and --tp-resize-target-coarse with 10 and 20 pixel fallbacks.

API Reference

Every element also accepts motion-policy.

Attributes

AttributePropertyTypeDefaultDescriptionminminnumber | undefined—A minimum percentage for every panel that does not declare its own min-size.keyboard-stepkeyboardStepnumber1How far, in pixels, an arrow key moves a handle.with-handlewithHandlebooleanfalseShows a grip on the generated handles; authored handles set their own.disable-cursordisableCursorbooleanfalseLeaves the document cursor alone while dragging, instead of showing the resize cursor everywhere.disable-double-click-resetdisableDoubleClickResetbooleanfalseStops a double-click on a handle from restoring its panel's default size.persistence-keypersistenceKeystring''The key the persistence adapter saves and restores the layout under.

Properties

PropertyTypeDefaultDescriptionsizesreadonly PanelExtent[]—The panel sizes in panel order; set it to control the layout, then publish each accepted proposal back. Numbers are pixels, unitless strings percentages.defaultSizesreadonly PanelExtent[] | undefined—The initial sizes in panel order, when the group owns its layout.defaultLayoutPanelLayout | undefined—The initial layout by panel id, as percentages, when the group owns its layout; survives reordering.resizeTargetMinimumSizeobject{ fine: 10, coarse: 20 }The smallest pointer target of each handle, in pixels, for fine and coarse pointers; the visible line stays thin.persistenceAdapterPanelPersistenceAdapter | undefined—Your storage for the layout: load(key) restores it before the first commit, save(key, layout) stores each settled layout. Controlled groups never use it.onSizesChange((event: TpValueChangeEvent<readonly number[]>) => void) | undefined—Called with each size proposal, as tp-value-change is dispatched.onLayoutChange((layout: PanelLayout) => void) | undefined—Called with the layout after each commit, as tp-layout-change is dispatched.onLayoutChanged((layout: PanelLayout) => void) | undefined—Called with the final layout once a resize settles, as tp-layout-change-complete is dispatched.

Events

EventTypeDescriptiontp-value-changeTpValueChangeEvent<readonly number[]>Cancelable proposal of new panel sizes, in pixels and panel order, before it commits; a controlled group must publish the accepted sizes.tp-layout-changeCustomEvent<{ layout: Readonly<Record<string, number>>; sizes: number[]; }>After each committed layout, with layout (panel id to percentage) and sizes (pixels).tp-layout-change-completeCustomEvent<{ layout: Readonly<Record<string, number>>; sizes: number[]; }>Once a resize settles, with the final layout and sizes; the place to save a layout.

Methods

MethodReturnsDescriptiongetLayout()PanelLayoutThe current layout: each panel id with its size as a percentage.setLayout(layout)PanelLayoutProposes a layout by panel id, in percentages, and returns the layout actually committed after the bounds apply.getPanelSize(panel)PanelSizeA panel's current size, in pixels and as a percentage.getPanelElement(id)HTMLElement | undefinedThe panel with this id, if the group holds one.collapsePanel(panel)voidCollapses a collapsible panel to its collapsed size.expandPanel(panel)voidExpands a collapsed panel to its last expanded size, else its default or minimum.resizePanel(panel, size)voidResizes one panel within its bounds; its neighbors absorb the difference.

Slots

SlotDescriptiondefaultPanels and handles, as direct children in their logical order.

CSS parts

PartDescription::part(resizable-panel-group)The flex container laying the panels out along the group axis.::part(resizable-panel-group-panel)A panel; marked while collapsed.

<tp-resizable-panel>

Attributes

AttributePropertyTypeDefaultDescriptiondefault-sizedefaultSizePanelExtent | undefined—The size the panel starts at and returns to on a double-click of its handle; without it, the panel shares the remaining space.min-sizeminSizePanelExtent | undefined—The smallest size a resize leaves the panel at, unless it collapses.max-sizemaxSizePanelExtent | undefined—The largest size a resize gives the panel.collapsed-sizecollapsedSizePanelExtent0The size of the panel while collapsed, between zero and its minimum.collapsiblecollapsiblebooleanfalseLets a resize past the minimum snap the panel to its collapsed size.resize-behaviorresizeBehavior'relative' | 'preserve-pixel-size''relative'How the panel answers a change in the group's size: keep its share (relative) or keep its pixel size, as a fixed sidebar does. At least one panel keeps its share.

Properties

PropertyTypeDefaultDescriptiononResize((size: PanelSize, id: string, previous: PanelSize | undefined) => void) | undefined—Called after the panel's committed size changes, as tp-panel-resize is dispatched.ownerGroupread-onlyTpResizablePanelGroup | null—The group the panel belongs to: its parent, when that is a Resizable panel group.

Events

EventTypeDescriptiontp-resizable-member-changeEventBubbles when the panel's configuration changes, so its group lays out again.tp-panel-resizeCustomEvent<{ size: { inPixels: number; asPercentage: number; }; previousSize: { inPixels: number; asPercentage: number; } | undefined; id: string; }>After the panel's committed size changes, with size and previousSize (each in pixels and as a percentage) and the panel id.

Methods

MethodReturnsDescriptioncollapse()voidCollapses the panel, when it is collapsible.expand()voidExpands the collapsed panel to its last expanded size, else its default or minimum.resize(size)voidResizes the panel within its bounds; its neighbors absorb the difference.getSize()PanelSizeThe panel's current size, in pixels and as a percentage of the group.isCollapsed()booleanWhether the panel is at its collapsed size.

Slots

SlotDescriptiondefaultThe panel content.

CSS parts

PartDescription::part(resizable-panel-group-panel)A panel; marked while collapsed.

<tp-resizable-handle>

Attributes

AttributePropertyTypeDefaultDescriptionwith-handlewithHandlebooleanfalseShows a grip on the line, a visible cue that it can be dragged.targettargetstring''The id of the adjacent panel the handle describes and adjusts with the keyboard; the preceding panel by default.labellabelstring'Resize panels'The accessible name of the separator, such as "Resize sidebar".disable-double-click-resetdisableDoubleClickResetbooleanfalseStops a double-click on this handle from restoring its panel's default size.

Properties

PropertyTypeDefaultDescriptionownerGroupTpResizablePanelGroup | undefined—The group a generated handle belongs to, which is not its parent; set by the group.boundarynumber-1The index of the boundary between panels the handle moves; set by the group.groupread-onlyTpResizablePanelGroup | null—The group the handle belongs to.separatorElementread-onlyHTMLElement | null—The focusable separator element, for focusing the handle from code.

Events

EventTypeDescriptiontp-resizable-member-changeEventBubbles when the handle's configuration changes, so its group lays out again.

CSS parts

PartDescription::part(resizable-panel-group-handle-decoration)The grip a handle shows with with-handle.::part(resizable-panel-group-separator)The focusable separator of a handle, with its enlarged pointer target; marked while dragging or disabled.