Resizable
Panels separated by handles you drag or move with the arrow keys.
Panels
Drag a handle, or focus it with Tab and use the arrow keys. Groups nest.
Code
<ResizablePanelGroup orientation="horizontal">
<ResizablePanel defaultSize="50%">One</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">Two</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">Three</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>Usage
import { ResizablePanelGroup, ResizablePanel, ResizableHandle, ResizableGrid, ResizableCell, ResizableGridline } from "@elements/ui/resizable";
<ResizablePanelGroup orientation="horizontal" autoSaveId="mail">
<ResizablePanel defaultSize="25%" minSize="15%" collapsible>Folders</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel>Messages</ResizablePanel>
</ResizablePanelGroup>
<!-- layout and collapsed are two-way -->
<ResizablePanelGroup layout={state.layout}>...</ResizablePanelGroup>
<ResizablePanel collapsible collapsed={state.sideClosed}>...</ResizablePanel>
<!-- columns and rows at once -->
<ResizableGrid columns={3} rows={2}>
<ResizableCell column={0} row={0} columnSpan={2}>Header</ResizableCell>
<ResizableCell column={2} row={0} rowSpan={2}>Inspector</ResizableCell>
<ResizableCell column={0} row={1}>Files</ResizableCell>
<ResizableCell column={1} row={1}>Editor</ResizableCell>
<ResizableGridline type="row" row={1} columnSpan={2} />
<ResizableGridline type="column" column={1} row={1} />
<ResizableGridline type="column" column={2} />
</ResizableGrid>Examples
Vertical
orientation vertical stacks the panels; ArrowUp and ArrowDown move the handle.
Code
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">Header</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">Content</ResizablePanel>
</ResizablePanelGroup>Handle
withHandle shows a grip on the hairline. Double click resets the size.
Code
<!-- A visible label for the primary pane names the separator (APG) -->
<ResizablePanelGroup>
<ResizablePanel defaultSize="25%"><h4 id="sidebar-label">Sidebar</h4></ResizablePanel>
<ResizableHandle withHandle label="" attrs={{ "aria-labelledby": "sidebar-label" }} />
<ResizablePanel defaultSize="75%">Content</ResizablePanel>
</ResizablePanelGroup>No double-click reset
disableDoubleClick keeps the size a user chose when the handle is double clicked.
Code
<ResizablePanelGroup>
<ResizablePanel defaultSize="25%">Sidebar</ResizablePanel>
<ResizableHandle withHandle disableDoubleClick />
<ResizablePanel defaultSize="75%">Content</ResizablePanel>
</ResizablePanelGroup>Collapsible
Push the sidebar past its minimum and it collapses to a rail; Enter on the handle collapses and restores it. collapsed is two-way.
Code
<ResizablePanelGroup>
<ResizablePanel defaultSize="30%" minSize="20%" maxSize="45%"
collapsible collapsedSize="6%" collapsed={state.sideClosed}>
...
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel minSize="30%">Content</ResizablePanel>
</ResizablePanelGroup>
<Button onclick={() => state.sideClosed = !state.sideClosed}>Toggle</Button>Conditional panels
Panels and handles can come and go with e:if. The group lays out the panels that remain from their default sizes, and the panel without one takes the rest.
Code
<ResizablePanelGroup>
<ResizablePanel e:if={state.showLeft} id="sources" defaultSize="25%" minSize="15%">Sources</ResizablePanel>
<ResizableHandle e:if={state.showLeft} />
<ResizablePanel id="editor" minSize="30%">Editor</ResizablePanel>
<ResizableHandle e:if={state.showRight} />
<ResizablePanel e:if={state.showRight} id="console" defaultSize="25%" minSize="15%">Console</ResizablePanel>
</ResizablePanelGroup>Collapse threshold
collapsedThreshold sets how far under its minimum a panel is squeezed before it collapses, and how far it is pulled before it opens again. disableCursor keeps the page cursor as it is during a drag.
Code
<!-- Collapses once squeezed 5% under its minimum (the default is halfway to
collapsedSize). disableCursor leaves the page cursor alone while dragging. -->
<ResizablePanelGroup disableCursor>
<ResizablePanel defaultSize="35%" minSize="30%" collapsible collapsedThreshold="5%">
Outline
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel>Document</ResizablePanel>
</ResizablePanelGroup>Controlled layout
Bind layout to read or set every panel's percent. onLayoutChanged fires once per change, with whether the user made it.
Code
<ResizablePanelGroup layout={state.layout}
onLayoutChanged={(layout, meta) => save(layout)}>
<ResizablePanel>Left</ResizablePanel>
<ResizableHandle />
<ResizablePanel>Right</ResizablePanel>
</ResizablePanelGroup>
<Button onclick={() => state.layout = [30, 70]}>30 / 70</Button>Limits and units
Each panel keeps 20% at least, so a handle pushes past its neighbor. A number is px: the sidebar below keeps 160px as the window resizes.
Code
<ResizablePanelGroup>
<ResizablePanel minSize="20%" maxSize="60%">A</ResizablePanel>
<ResizableHandle />
<ResizablePanel minSize="20%">B</ResizablePanel>
<ResizableHandle />
<ResizablePanel minSize="20%">C</ResizablePanel>
</ResizablePanelGroup>
<!-- px: a number is px; the sidebar keeps 160px as the group resizes -->
<ResizablePanelGroup>
<ResizablePanel defaultSize={160} minSize="7.5rem" groupResizeBehavior="preserve-pixel-size">Sidebar</ResizablePanel>
<ResizableHandle />
<ResizablePanel>Content</ResizablePanel>
</ResizablePanelGroup>Remembered
autoSaveId keeps the layout in localStorage: resize, then reload.
Code
<ResizablePanelGroup autoSaveId="editor">
<ResizablePanel id="files" defaultSize="30%">Files</ResizablePanel>
<ResizableHandle />
<ResizablePanel id="editor">Editor</ResizablePanel>
</ResizablePanelGroup>Disabled
A disabled group does not resize; a disabled handle does not, while its neighbors still can. A disabled panel keeps its size: neither of its handles moves it, and nothing pushes it.
Code
<ResizablePanelGroup disabled>...</ResizablePanelGroup>
<ResizableHandle disabled />
<!-- A disabled panel keeps its size: no handle resizes it, and its
neighbors cannot push it. -->
<ResizablePanelGroup>
<ResizablePanel defaultSize="30%">One</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="40%" disabled>Locked</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="30%">Three</ResizablePanel>
</ResizablePanelGroup>Separator preview
resizePreviewMode separator: the separator follows the pointer within the limits and the panels resize on release. Escape cancels. onResize gets the size in percent and px, the panel id and the size before. Below, preview panel on the second handle makes it resize live while the first still previews.
Code
<ResizablePanelGroup resizePreviewMode="separator">
<ResizablePanel defaultSize="40%" minSize="20%"
onResize={(size, id, prev) => show(size, id, prev)}>
Preview
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel minSize="20%">Content</ResizablePanel>
</ResizablePanelGroup>
<!-- preview on one separator overrides the group's mode -->
<ResizablePanelGroup resizePreviewMode="separator">
<ResizablePanel>Preview</ResizablePanel>
<ResizableHandle />
<ResizablePanel>Live</ResizablePanel>
<ResizableHandle preview="panel" />
<ResizablePanel>Content</ResizablePanel>
</ResizablePanelGroup>No separators
Separators are optional. Without one, drag the edge where two panels meet; the cursor shows where it takes.
Code
<!-- Separators are optional: drag the edge where two panels meet -->
<ResizablePanelGroup>
<ResizablePanel minSize="20%">Left</ResizablePanel>
<ResizablePanel minSize="20%">Right</ResizablePanel>
</ResizablePanelGroup>Fixed splitter
A fixed size splitter (APG) has two positions. The arrow keys do nothing; Enter or a click collapses the pane and restores it.
Code
<!-- APG fixed size splitter: no arrow keys or dragging; Enter or a click
toggles the pane between its size and collapsed -->
<ResizablePanelGroup>
<ResizablePanel defaultSize="30%" minSize="30%" maxSize="30%" collapsible collapsedSize="0%">
Navigation
</ResizablePanel>
<ResizableHandle withHandle fixed />
<ResizablePanel>Content</ResizablePanel>
</ResizablePanelGroup>Grid
ResizableGrid sizes columns and rows at once. Cells may span tracks; gridlines run beside a spanning cell, never across it. Drag where two gridlines cross to resize both axes. The Files column collapses past its minimum, and Enter on its gridline collapses and restores it. A disabled gridline does not resize.
Code
<!-- Header spans two columns and Info two rows, so the gridlines run
beside them, never across them. Drag where two gridlines cross to
resize both axes. -->
<ResizableGrid
columns={[
{ defaultSize: "25%", minSize: "15%", collapsible: true },
{ minSize: "20%" },
{ defaultSize: "25%", minSize: "15%" },
]}
rows={[{ defaultSize: "30%", minSize: "15%" }, { minSize: "20%" }]}
onLayoutChanged={(layout, meta) => save(layout)}>
<ResizableCell column={0} row={0} columnSpan={2}>Header</ResizableCell>
<ResizableCell column={2} row={0} rowSpan={2}>Info</ResizableCell>
<ResizableCell column={0} row={1}>Files</ResizableCell>
<ResizableCell column={1} row={1}>Editor</ResizableCell>
<ResizableGridline type="row" row={1} columnSpan={2} label="Header height" />
<ResizableGridline type="column" column={1} row={1} label="Files width" />
<ResizableGridline type="column" column={2} withHandle label="Info width" />
</ResizableGrid>Grid without gridlines
Gridlines are optional. Without them, drag the edge where two cells meet, or the middle where four meet. layout is two-way: Even sets it. A disabled grid does not resize. disableCursor leaves the cursor alone.
Code
<!-- No gridlines: drag the edges where cells meet, or the middle where
all four meet. layout is two-way. -->
<ResizableGrid columns={2} rows={2} layout={state.gridLayout}>
<ResizableCell column={0} row={0}>One</ResizableCell>
<ResizableCell column={1} row={0}>Two</ResizableCell>
<ResizableCell column={0} row={1}>Three</ResizableCell>
<ResizableCell column={1} row={1}>Four</ResizableCell>
</ResizableGrid>
<Button onclick={() => state.gridLayout = { columns: [50, 50], rows: [50, 50] }}>Even</Button>Right to left
Under rtl the first panel is on the right; arrows and drags follow what you see.
Code
<ResizablePanelGroup dir="rtl">
<ResizablePanel defaultSize="30%">واحد</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel>اثنان</ResizablePanel>
</ResizablePanelGroup>