Preview
Usage
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.
Examples
Two panels
Three panels
Vertical
Nested layouts
Resizable groups can be nested. For example, a horizontal sidebar + content layout where the content area is itself a vertical split:
Best practices
Do
- Set
initial-sizeon panels with a known width (sidebars, detail panes). Omit it on the main content panel so it fills the remaining space. - Set
user-min-sizeon every panel. Without it, panels can shrink to nearly zero. A minimum of"300"(192px) keeps most content usable. - Use
storage-keyto persist layouts. Users expect their panel arrangement to survive a page refresh. - Always provide a way to restore collapsed panels — for example, a menu icon button in a sibling panel's header.
Don’t
- Don't pass raw pixel values for sizes. Only percentage tokens (
"25p") and Dialtone layout tokens ("500") are accepted. - Don't set
initial-sizeon every panel. Leave one panel without it so it absorbs remaining space and the layout always fills the container. - Don't hide a panel without giving the user a way to bring it back. Provide an expand control in a visible sibling panel (e.g., a menu button in the content header).
Constraints
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 constraints
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.
System constraints
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.
Fixed panels
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.
Collapsing panels
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.
<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.
Dynamic constraints on collapse
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:
Auto-collapse rules
Use collapse-rules to define which panels collapse first when space gets tight. Lower priority numbers collapse first.
Persisting panel sizes
Add a storage-key to save panel sizes to localStorage automatically. Users resize once, and the layout restores on their next visit.
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(); },
};
When both storage-key and :storage are provided, the custom adapter takes precedence.
Space allocation strategies
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.
Offset from fixed elements
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.
Alternatively, use offset-amount for an explicit pixel value without measuring an element. If both are provided, offset-amount takes precedence.
Accessibility
Keyboard navigation
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.
Vue API
DtResizable
import { Resizable } from '@dialpad/dialtone-vue';Slots
Props
Events
DtResizablePanel
import { DtResizable_panel } from '@dialpad/dialtone-vue';DtResizableHandle
import { DtResizable_handle } from '@dialpad/dialtone-vue';