elements
Demos UI Feed Discuss Install
Style System
  • Style
  • Themes
  • Behaviors
Forms
  • Button
  • ButtonGroup
  • Checkbox
  • ColorPicker
  • Combobox
  • Field
  • FileUpload
  • Input
  • InputGroup
  • InputOTP
  • Label
  • NativeSelect
  • NumberInput
  • PasswordInput
  • RadioGroup
  • Select
  • Slider
  • Switch
  • TagsInput
  • Textarea
  • Toggle
  • ToggleGroup
Overlays
  • AlertDialog
  • Command
  • ContextMenu
  • Dialog
  • Drawer
  • DropdownMenu
  • HoverCard
  • Menubar
  • NavigationMenu
  • Popover
  • Sheet
  • Toast
  • Tooltip
Display
  • Accordion
  • Alert
  • AspectRatio
  • Avatar
  • Badge
  • Breadcrumb
  • Card
  • Collapsible
  • Empty
  • Item
  • Kbd
  • Pagination
  • Progress
  • Separator
  • Skeleton
  • Spinner
  • Table
  • Tabs
  • Tree
Layout
  • Carousel
  • Resizable
  • ScrollArea
  • Sidebar
Dates
  • Calendar
  • DatePicker
  • DateRangePicker
  • TimePicker
Data
  • AreaChart
  • BarChart
  • Chart
  • DataTable
  • Kanban
  • LineChart
  • PieChart
  • RadarChart
  • RadialChart
  • VirtualList
Chat
  • Attachment
  • Message
  • MessageList
  • PromptInput
Elements UI / Layout / Resizable

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.

One
Two
Three
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.

Header
Content
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.

Sidebar
Content
dragging: false
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.

Sidebar
Content
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.

Inbox, Drafts, Sent
Content
collapsed: false, onCollapse: 0, onExpand: 0
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.

Sources
Editor
Console
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.

Outline
Document
collapsed: false
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.

Left
Right
layout: 50 / 50; changed: 0; user: moves: 0 requested:
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.

A
B
C
Sidebar
Content
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.

Files
Editor
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.

Fixed
Fixed
One
Two
Three
One
Locked
Three
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.

Preview
Content
size:
Preview
Live
Content
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.

Left
Right
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.

Navigation
Content
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.

Header
Info
Files
Editor
Resize to see onLayoutChanged. onLayoutChange: 0
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.

One
Two
Three
Four
30 / 70 | 60 / 40
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>
Reference Resizable in the manual Every part, attribute, slot, key and data attribute.
Prev CarouselNext ScrollArea
Product
Manual Pricing Compare
Company
History Contact
Legal
Privacy Policy Acceptable Use Terms License Agreement
© Elements Dev Corporation 2026