Drag-to-resize split panel component.
Provides resizable/1, resizable_panel/1, and resizable_handle/1
sub-components that together create a resizable split-pane layout. The
PhiaResizable JavaScript hook handles mouse drag, touch drag, and
keyboard arrow-key resizing with configurable min/max size clamping.
Sub-components
| Component | Element | Purpose |
|---|---|---|
resizable/1 | <div> | Flex container with the PhiaResizable hook attached |
resizable_panel/1 | <div> | Individual panel sized via CSS flex |
resizable_handle/1 | <div> | Drag handle between panels (keyboard focusable) |
Hook setup
Register the PhiaResizable hook in your LiveSocket before using this
component:
// assets/js/app.js
import PhiaResizable from "./hooks/resizable"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { PhiaResizable }
})Horizontal split (default)
Side-by-side panels, the most common layout for code editors, detail/list splits, and side-by-side comparisons:
<.resizable class="h-[400px]">
<.resizable_panel default_size={50} min_size={20}>
<div class="h-full p-4">Left panel</div>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={50} min_size={20}>
<div class="h-full p-4">Right panel</div>
</.resizable_panel>
</.resizable>Vertical split
Top-and-bottom panels, useful for query editors, terminal splits, or primary + detail layouts:
<.resizable direction="vertical" class="h-[600px]">
<.resizable_panel default_size={60} min_size={30}>
<div class="h-full overflow-auto p-4">Top panel (query editor)</div>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={40} min_size={20}>
<div class="h-full overflow-auto p-4">Bottom panel (results)</div>
</.resizable_panel>
</.resizable>Three-column layout (master-detail-info)
<.resizable class="h-screen">
<.resizable_panel default_size={20} min_size={15} max_size={30}>
<%!-- Sidebar navigation --%>
<nav class="h-full overflow-y-auto p-4">...</nav>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={50} min_size={30}>
<%!-- Main content --%>
<main class="h-full overflow-y-auto p-4">...</main>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={30} min_size={20} max_size={40}>
<%!-- Detail/properties panel --%>
<aside class="h-full overflow-y-auto p-4">...</aside>
</.resizable_panel>
</.resizable>IDE-style layout (file tree + editor + terminal)
<.resizable direction="vertical" class="h-screen">
<.resizable_panel default_size={70}>
<.resizable class="h-full">
<.resizable_panel default_size={20} min_size={15}>
<div class="h-full p-2">File tree</div>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={80}>
<div class="h-full p-4">Editor</div>
</.resizable_panel>
</.resizable>
</.resizable_panel>
<.resizable_handle />
<.resizable_panel default_size={30} min_size={10}>
<div class="h-full p-2 font-mono text-sm">Terminal</div>
</.resizable_panel>
</.resizable>Panel size model
Panel sizes are percentages (0–100). The CSS flex property is set to
flex: N 1 0% where N is the default_size. The PhiaResizable hook
reads data-min-size and data-max-size from each panel to clamp dragging.
Accessibility
- The
resizable_handle/1carriesrole="separator"andtabindex="0"so keyboard users can focus it and adjust the split with arrow keys. aria-valuenow,aria-valuemin, andaria-valuemaxcommunicate the current split position to assistive technologies.
Summary
Functions
Renders the resizable container.
Renders the drag handle between two resizable panels.
Renders a resizable panel inside a resizable/1 container.
Functions
Renders the resizable container.
Attaches the PhiaResizable JavaScript hook via phx-hook. The hook
reads the data-direction attribute to determine drag axis. Requires the
PhiaResizable hook to be registered in your LiveSocket hooks.
Always set an explicit height on the container so the panels have space
to fill — the container uses h-full w-full internally:
<.resizable class="h-[500px]">
...
</.resizable>Attributes
direction(:string) - Split direction:"horizontal"(panels side-by-side) or"vertical"(panels top-and-bottom). Defaults to"horizontal". Must be one of"horizontal", or"vertical".class(:string) - Additional CSS classes applied to the container<div>. Always set an explicit height (e.g.class="h-[400px]") so panels have space to fill. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the container
<div>element.
Slots
inner_block(required) - Alternatingresizable_panel/1andresizable_handle/1children.
Renders the drag handle between two resizable panels.
The handle is a thin <div> with a 1px bg-border line and an expanded
interactive hit area via an ::after pseudo-element. It is keyboard
focusable (tabindex="0") and carries ARIA separator attributes.
Place one handle between each pair of adjacent panels:
<.resizable_panel ...>...</.resizable_panel>
<.resizable_handle /> <%!-- between panels --%>
<.resizable_panel ...>...</.resizable_panel>Keyboard interaction (managed by the PhiaResizable hook):
ArrowLeft/ArrowRight— adjust horizontal split.ArrowUp/ArrowDown— adjust vertical split.
Attributes
class(:string) - Additional CSS classes applied to the handle<div>. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the handle
<div>element.
Renders a resizable panel inside a resizable/1 container.
The panel's initial size is set via CSS flex: N 1 0% where N is the
default_size. The hook stores data-default-size, data-min-size, and
data-max-size for runtime clamping during drag operations.
Panels use overflow-hidden by default to prevent content from breaking
the layout. Override with class="overflow-auto" when the panel content
should scroll independently.
Example
<.resizable_panel default_size={40} min_size={20} max_size={70}>
<div class="h-full overflow-auto p-4">
Panel content
</div>
</.resizable_panel>Attributes
default_size(:integer) - Initial panel size as a percentage of the container (0–100). The sum of all paneldefault_sizevalues should equal 100. Defaults to50.min_size(:integer) - Minimum panel size as a percentage. The hook clamps dragging so this panel cannot shrink below this value. Defaults to10.max_size(:integer) - Maximum panel size as a percentage. The hook clamps dragging so this panel cannot grow beyond this value. Defaults to90.class(:string) - Additional CSS classes applied to the panel<div>. Typicallyoverflow-autooroverflow-hidden. Defaults tonil.- Global attributes are accepted. HTML attributes forwarded to the panel
<div>element.
Slots
inner_block(required) - Panel content.