Splitter
v0.0.10
mix mishka.ui.gen.headless splitter
Overview of the Phoenix and LiveView Headless Splitter Component
The Mishka Chelekom Headless Splitter is a Phoenix function component for LiveView: two panes with a draggable divider between them. The root div carries the Splitter hook and holds a first panel, a resizer with role="separator" and a second panel. The hook writes the current split, as a percentage for the first panel, to the CSS variable --chelekom-splitter-pos on the root. You size the first panel from that variable in your own CSS.
The divider moves with the pointer or the keyboard, and data-dragging is set on the root while a drag is in progress. data-orientation and data-disabled are on the root too. Style the parts with class, panel_class and resizer_class.
Base UI Splitter example - files and editor panes (Phoenix headless)
Base UI Splitter example - files and editor panes (Phoenix headless)
<.splitter
id="baseui-splitter"
default_size={45}
class="flex h-40 w-full max-w-80 overflow-hidden rounded-lg border border-neutral-300 dark:border-neutral-700 [&_[data-part=panel][data-index='0']]:w-[var(--chelekom-splitter-pos)] [&_[data-part=panel][data-index='0']]:shrink-0 [&_[data-part=panel][data-index='1']]:flex-1 [&_[data-part=panel][data-index='1']]:bg-neutral-50 dark:[&_[data-part=panel][data-index='1']]:bg-neutral-800"
panel_class="min-w-0 overflow-auto"
resizer_class="w-1.5 shrink-0 cursor-col-resize bg-neutral-200 outline-none hover:bg-neutral-400 focus-visible:bg-neutral-500 dark:bg-neutral-700 dark:hover:bg-neutral-500 dark:focus-visible:bg-neutral-400"
>
<:first>
<div class="p-3 text-sm text-neutral-700 dark:text-neutral-300">Files</div>
</:first>
<:second>
<div class="p-3 text-sm text-neutral-700 dark:text-neutral-300">
Editor: drag the divider, or focus it and use the arrow keys.
</div>
</:second>
</.splitter>Attributes and events
id is required because it carries the hook. orientation is horizontal (panes side by side, the default) or vertical (panes stacked). default_size is the starting size of the first panel in percent and defaults to 50. The first panel is kept between min (10) and max (90). label names the resizer for assistive technology and defaults to "Resize panels".
disabled turns off dragging and keys, sets data-disabled on the root and takes the resizer out of the tab order. on_change is a LiveView event name, pushed with the new size as a whole percent, for example %{"value" => 45}, each time the divider moves.
A vertical splitter stacks the panes with flex-col on the root and sizes the first pane's height from the same variable:
<.splitter
id="stacked"
orientation="vertical"
class="flex h-64 w-full flex-col [&_[data-part=panel][data-index='0']]:h-[var(--chelekom-splitter-pos)] [&_[data-part=panel][data-index='0']]:shrink-0 [&_[data-part=panel][data-index='1']]:flex-1"
panel_class="min-h-0 overflow-auto"
resizer_class="h-1.5 shrink-0 cursor-row-resize bg-neutral-200"
>
<:first>Top</:first>
<:second>Bottom</:second>
</.splitter>To keep the size on the server, store the value from on_change and pass it back as default_size. After a server patch the hook puts the divider back where the last move left it, so a re-render does not fight a drag.
<.splitter
id="layout"
default_size={@size}
on_change="resize"
class="flex h-40 w-full max-w-80 [&_[data-part=panel][data-index='0']]:w-[var(--chelekom-splitter-pos)] [&_[data-part=panel][data-index='0']]:shrink-0 [&_[data-part=panel][data-index='1']]:flex-1"
>
<:first>Files</:first>
<:second>Editor</:second>
</.splitter>def handle_event("resize", %{"value" => value}, socket) do
{:noreply, assign(socket, size: value)}
endParts & data attributes
Each part carries a stable data-part and a structural chelekom-splitter* class you can target.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | data-orientation, data-disabled, data-dragging, data-min, data-max, data-on-change, --chelekom-splitter-pos | Carries the hook and the split. |
| Panel | panel | data-index="0" or "1" | The first and the second pane. |
| Resizer | resizer | role="separator", aria-orientation, aria-valuemin, aria-valuemax, aria-valuenow, aria-label, tabindex | The divider. Drag it, or focus it and use the keys. |
Accessibility
Follows the WAI-ARIA Window Splitter pattern. The resizer is a focusable separator whose aria-valuenow is the first panel's size in percent, and its aria-orientation is perpendicular to the split. With it focused, the arrow keys move the divider by 1 percent (10 with Shift), Home goes to min and End goes to max. Dragging uses pointer events, so mouse, pen and touch all work. Give the resizer a visible focus style, as the example does with focus-visible.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id (carries the Splitter hook) |
orientation
|
string
|
horizontal
|
horizontalvertical
| No | Split axis |
default_size
|
integer
|
50
| — | No | Initial size of the first panel (percent) |
min
|
integer
|
10
| — | No | Minimum first-panel size (percent) |
max
|
integer
|
90
| — | No | Maximum first-panel size (percent) |
disabled
|
boolean
|
false
| — | No | Disable resizing (data-disabled) |
label
|
string
|
Resize panels
| — | No | Accessible label for the resizer |
on_change
|
string
| — | — | No | LiveView event pushed on resize ({value}) |
class
|
any
| — | — | No | Extra classes for the root |
panel_class
|
any
| — | — | No | Extra classes for each panel |
resizer_class
|
any
| — | — | No | Extra classes for the resizer |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
first
| Yes | The first (leading) panel |
second
| Yes | The second (trailing) panel |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
splitter
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless splitter