Resize handle
Resize handle
A resize handle is a draggable boundary between a sized element and whatever sits next to it.
Use it wherever a panel’s width or height is the user’s to decide: a document viewer sidebar, a list-and-detail split, a console docked under a preview.
The framework supplies the boundary’s appearance and the sizing hook.
The drag itself needs script, so reach for PvResizeHandle (Vue) or <pv-resize-handle> (web component) rather than wiring pointer events by hand.
Structure
Add pv-resizable to the element you want to resize, then place a handle inside it.
The handle writes two things onto that element: a --pv-resize-size custom property holding the resolved pixel size, and a data-pv-resize-axis attribute saying whether that property should drive width or height.
HTML
<div class="pv-resizable" data-pv-resize-axis="horizontal" style="--pv-resize-size: 280px">
<!-- panel content -->
<pv-resize-handle min-size="160" max-size="560" default-size="280"></pv-resize-handle>
</div>
The parent of a pv-resizable element is what percentages resolve against, so a min-size of 20% means twenty percent of the containing block, re-resolved whenever that block changes size.
A percentage default waits until that container is measurable, including when a tab or accordion starts hidden.
Model updates are clamped to the same bounds as pointer and keyboard resizing, including when controlled is set.
A valid collapsed size remains an exception to the expanded minimum.
Persistence records both pixels and collapsed state so percentage collapse survives container changes and reloads; older numeric storage values still load.
Keep scrolling off the pv-resizable element itself.
The handle is positioned against it, so making it the scroll container would carry the handle away with the content.
Put the overflow on a wrapper inside it instead.
Edges
The handle sits on one edge of the element it resizes, given by the edge property: right (the default), left, top, or bottom.
Left and right edges resize width and draw a vertical rule; top and bottom edges resize height and draw a horizontal one.
Pick the edge that actually faces the space being traded.
A drawer docked against the right side of the page is resized from its left edge, and one docked on the left is resized from its right.
Hit area
The visible rule is a hairline, but the pointer target around it is 11px wide, because a 1px target is not something anyone can reliably grab.
The target sits inside the resized element rather than straddling its edge, so it survives an element that clips or scrolls.
Both are adjustable per instance:
HTML
<div class="pv-resizable" style="--pv-resize-handle-hit-area: 16px; --pv-resize-handle-rule-size: 2px">
<!-- panel content -->
</div>
| Custom property | Default | Purpose |
|---|---|---|
--pv-resize-size |
none | Resolved size, written by the handle |
--pv-resize-handle-hit-area |
11px |
Pointer target thickness |
--pv-resize-handle-rule-size |
1px |
Visible rule thickness at rest |
--pv-resize-handle-rule-size-active |
2px |
Visible rule thickness on hover and drag |
--pv-resize-handle-rule-color |
$color-border |
Rule color at rest |
--pv-resize-handle-rule-color-active |
$color-border-accent |
Rule color on hover, focus, and drag |
Split panes
For a straightforward two-panel split, pv-split-pane wraps the whole arrangement.
Its start panel carries the size and its end panel fills the remainder, which keeps one source of truth for the boundary.
HTML
<div class="pv-split-pane" data-pv-split-pane-direction="horizontal">
<div class="pv-split-pane-panel pv-resizable">
<div class="pv-split-pane-content">
<!-- start panel -->
</div>
</div>
<div class="pv-split-pane-panel pv-split-pane-panel-fill">
<!-- end panel -->
</div>
</div>
The start content has its own clipping wrapper so collapsing it cannot spill content over the end panel or hide the resize handle.
Collapsed content remains mounted but is hidden from view and keyboard navigation.
Build a three-column layout by nesting two split panes rather than looking for a third panel.
Components that already carry a handle
Containers in the library that own their own width take the same behavior through optional props, so opting an existing layout in is a prop rather than a rewrite.
PvDrawer takes resizable, along with defaultWidth, minWidth, maxWidth, resizeEdge, and resizeStorageKey.
The drawer defaults to resizeEdge="left" for its right-side dock.
Its percentage widths and default maximum resolve against the viewport, not the element containing the drawer in the DOM.
Without an explicit or persisted width, a resizable drawer starts at 600px, capped by the viewport.
Set resizeEdge="right" if you dock it on the left.
PvSidePanel takes resizableLeftSidebar and resizableRightSidebar, each with its own ...DefaultWidth, ...MinWidth, ...MaxWidth, and ...StorageKey.
A resizable sidebar drops the layout’s own border in favor of the handle’s rule, so the boundary the user drags is the boundary they see.
Both expose the live size through a model binding rather than an event, which keeps the number readable without wiring up a listener.
HTML
<PvSidePanel
v-model:leftSidebarWidth="leftWidth"
leftSidebarMinWidth="200"
leftSidebarStorageKey="contract-nav-width"
resizableLeftSidebar
/>
Accessibility
The handle is a focusable separator, so it is reachable and operable without a pointer.
- Arrow keys along the handle’s axis move the boundary by
keyboardSteppixels, and holding Shift multiplies that by four. - Home and End jump to the minimum and maximum.
- Enter and Space toggle collapse when the handle is collapsible.
Reopening restores the last expanded size from this session, or the default size after a reload, clamped to current bounds. - The handle reports
role="separator"witharia-orientation,aria-valuenow,aria-valuemin, andaria-valuemax, and announcesCollapsedwhen the panel is shut.
Give every handle an accessible name describing what it resizes, not what it is: “Resize contract list” rather than “Resize handle”.