The resizable component splits a container into adjustable panels separated by draggable handles. It works well for sidebar layouts, split-view editors, and any interface where users should control how space is distributed.
<dt-resizable> <dt-resizable-panel> ... </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel> ... </dt-resizable-panel> </dt-resizable>
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="sidebar" initial-size="25p"> Sidebar </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="content"> Main content </dt-resizable-panel> </dt-resizable> </dt-box>
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="ex3-sidebar" initial-size="20p" user-min-size="300"> Sidebar </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="ex3-content"> Content </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="ex3-details" initial-size="25p" user-min-size="300"> Details </dt-resizable-panel> </dt-resizable> </dt-box>
<dt-box block-size="400" inline-size="100p"> <dt-resizable direction="column"> <dt-resizable-panel id="exv-top" initial-size="40p"> Top Panel </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="exv-bottom"> Bottom Panel </dt-resizable-panel> </dt-resizable> </dt-box>
Resizable groups can be nested. For example, a horizontal sidebar + content layout where the content area is itself a vertical split:
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="ex-sidebar" initial-size="25p" user-min-size="300" user-max-size="50p"> Sidebar </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="content"> <dt-resizable direction="column"> <dt-resizable-panel id="editor" initial-size="25p"> Content Start </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="terminal"> Content End </dt-resizable-panel> </dt-resizable> </dt-resizable-panel> </dt-resizable> </dt-box>
initial-size on panels with a known width (sidebars, detail panes). Omit it on the main content panel so it fills the remaining space.user-min-size on every panel. Without it, panels can shrink to nearly zero. A minimum of "300" (192px) keeps most content usable.storage-key to persist layouts. Users expect their panel arrangement to survive a page refresh."25p") and Dialtone layout tokens ("500") are accepted.initial-size on every panel. Leave one panel without it so it absorbs remaining space and the layout always fills the container.All size props accept two formats: percentage tokens (e.g., "25p" for 25% of the container) and Dialtone layout tokens (e.g., "500" which resolves to 320px). Raw pixel values are not accepted — the component resolves token values from Dialtone's --dt-layout-* CSS custom properties at runtime, matching DtBox and the d-w-* / d-h-* utility classes.
initial-size defines where a panel starts. For panels whose size should flex with the available space (like a main content area), omit initial-size and the panel will fill whatever space remains.
user-min-size and user-max-size set hard limits on how small or large a user can drag a panel. These are enforced during drag interactions.
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="sidebar" initial-size="30p" user-min-size="20p" user-max-size="50p" > Sidebar (min 20%, max 50%) </dt-resizable-panel> </dt-resizable> </dt-box>
system-min-size and system-max-size define the range the layout engine uses when redistributing space during viewport resizes. These default to the user constraints when not specified. System min must be >= user min, and system max must be <= user max.
Set :resizable="false" to fix a panel at its initial-size. Fixed panels cannot be dragged, and no handle is rendered between a fixed panel and its neighbor. The layout engine subtracts fixed panel widths first, then distributes the remaining space among resizable panels.
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="nav" initial-size="100" :resizable="false"> Navigation (64px, fixed) </dt-resizable-panel> <dt-resizable-panel id="content"> Content (fills remaining space) </dt-resizable-panel> </dt-resizable> </dt-box>
Mark a panel as collapsible to let it collapse to zero width. Use the collapsed prop for the initial state, or call collapsePanel() from a template ref.
<dt-box block-size="400" inline-size="100p"> <dt-resizable @panel-collapse="onPanelCollapse"> <dt-resizable-panel id="sidebar" initial-size="25p" user-min-size="300" collapsible :collapsed="isSidebarCollapsed" > Sidebar </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="content"> <header> <button @click="isSidebarCollapsed = !isSidebarCollapsed"> Toggle sidebar </button> </header> Content </dt-resizable-panel> </dt-resizable> </dt-box>
<script setup>
import { ref } from 'vue';
const isSidebarCollapsed = ref(false);
function onPanelCollapse (panelId, collapsed) {
if (panelId === 'sidebar') {
isSidebarCollapsed.value = collapsed;
}
}
</script>
Listen to @panel-collapse to keep your local state in sync — the panel can also be collapsed by the system (auto-collapse rules, viewport resize). Always provide a visible control in a sibling panel to restore a collapsed panel.
For layouts where a panel starts hidden (e.g., a detail pane that opens when an item is selected), bind initial-size to a computed value that changes based on collapsed state:
<dt-box block-size="400" inline-size="100p"> <dt-resizable> <dt-resizable-panel id="list" :initial-size="isDetailOpen ? '30p' : '100p'" > List </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="detail" :initial-size="isDetailOpen ? '70p' : '0p'" collapsible :collapsed="!isDetailOpen" > Detail </dt-resizable-panel> </dt-resizable> </dt-box>
Use collapse-rules to define which panels collapse first when space gets tight. Lower priority numbers collapse first.
<dt-box block-size="400" inline-size="100p"> <dt-resizable :collapse-rules="[ { panelId: 'details', priority: 1 }, { panelId: 'sidebar', priority: 2 }, ]" > ... </dt-resizable> </dt-box>
Add a storage-key to save panel sizes to localStorage automatically. Users resize once, and the layout restores on their next visit.
<dt-box block-size="400" inline-size="100p"> <dt-resizable storage-key="my-layout"> ... </dt-resizable> </dt-box>
For state management integration (Pinia, Vuex, or an API), implement the ResizableStorageAdapter interface and pass it via :storage:
const piniaAdapter = {
save(data) { layoutStore.setLayout(data); },
load() { return layoutStore.layout; },
clear() { layoutStore.clearLayout(); },
};
<dt-box block-size="400" inline-size="100p"> <dt-resizable :storage="piniaAdapter"> ... </dt-resizable> </dt-box>
When both storage-key and :storage are provided, the custom adapter takes precedence.
When a panel opens or closes, the remaining panels need to redistribute space. The space-allocation-strategy prop controls how:
proportional (default) — All non-collapsed panels give or take space proportionally based on their current size.preserve-manual — Panels that the user has manually resized keep their exact size. Only panels the user hasn't touched give up space.When a fixed or absolutely positioned element (like a toolbar or header) overlaps the resizable area, set offset-element on DtResizable to automatically offset all handles and panel content below it.
<dt-box block-size="400" inline-size="100p"> <dt-resizable offset-element="#doc-demo-toolbar"> <dt-box id="doc-demo-toolbar" padding-inline="200" surface="secondary-opaque" border-width-block-end="100" block-size="75" class="d-ps-absolute d-t-0 d-l-0 d-r-0 d-zi-base1 d-d-flex d-ai-center d-jc-center " > Fixed Toolbar (48px) </dt-box> <dt-resizable-panel id="exo-left" initial-size="50p" class="d-bgc-positive"> Left Panel </dt-resizable-panel> <dt-resizable-handle /> <dt-resizable-panel id="exo-right" initial-size="50p" class="d-bgc-warning-subtle"> Right Panel </dt-resizable-panel> </dt-resizable> </dt-box>
Alternatively, use offset-amount for an explicit pixel value without measuring an element. If both are provided, offset-amount takes precedence.
Each resize handle has role="separator" with aria-orientation, aria-valuenow, aria-valuemin, aria-valuemax, aria-controls, and aria-valuetext reflecting the current layout. Handles are always focusable (tabindex="0") and follow the W3C ARIA separator pattern.
| Key | Action |
|---|---|
| Arrow keys | Resize by 8px |
| Shift + Arrow | Resize by 24px |
| Ctrl/Cmd + Arrow | Resize by 1px |
| Enter | Collapse or expand the adjacent panel (if collapsible) |
| Home | Set panel to minimum size |
| End | Set panel to maximum size |
| R | Reset adjacent panels to initial sizes |
| Escape | Remove focus from the handle |
Size changes are announced to screen readers via an aria-live region. All announcement strings are configurable via the messages prop on DtResizable for i18n.
Double-clicking a handle resets the two adjacent panels to their initial size proportions.
import { Resizable } from '@dialpad/dialtone-vue';
Name
|
Type
|
|---|---|
default | Container for panels and handles. |
Name
|
Default
|
Type
|
|---|---|---|
class | '' | string|object|array Additional CSS classes applied to the container element. |
collapseRules | [] | array Rules defining which panels collapse first when space is constrained |
direction | 'row' |
"'row'"
|
"'column'"
Layout direction. 'row' for horizontal, 'column' for vertical. |
messages | {} | object i18n message overrides for screen reader announcements.
Accepts keys from ResizableKeyboardMessages. |
offsetAmount | null | number Explicit pixel offset. Overrides offsetElement measurement when both provided. |
offsetDirection | 'start' |
"'start'"
|
"'end'"
|
"'both'"
Which edge(s) the offset applies to. |
offsetElement | null | string CSS selector for a fixed element to offset handles and panel content from. |
panels | [] | array Panel configurations array. When provided, panels are initialized
from this array instead of registering via child DtResizablePanel components. |
spaceAllocationStrategy | 'proportional' |
"'proportional'"
|
"'preserve-manual'"
Strategy for redistributing space when panels open/close. |
storage | null | object Custom storage adapter. Overrides storageKey when both are provided. |
storageKey | null | string localStorage key for persisting panel sizes across page loads. |
Name
|
Type
|
|---|---|
panel-collapse | |
panel-resize | |
resize-end | |
resize-start |
import { DtResizable_panel } from '@dialpad/dialtone-vue';import { DtResizable_handle } from '@dialpad/dialtone-vue';Resizable documentation last updated Friday, September 4, 2026
fix/popover-modal-zindex-scope