Splitter

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless splitter
Source View Source

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)

Files
Editor: drag the divider, or focus it and use the arrow keys.
heex
<.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:

heex
<.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.

heex
<.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>
elixir
def handle_event("resize", %{"value" => value}, socket) do
  {:noreply, assign(socket, size: value)}
end

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-splitter* class you can target.

Partdata-partState / ARIAPurpose
Rootrootdata-orientation, data-disabled, data-dragging, data-min, data-max, data-on-change, --chelekom-splitter-posCarries the hook and the split.
Panelpaneldata-index="0" or "1"The first and the second pane.
Resizerresizerrole="separator", aria-orientation, aria-valuemin, aria-valuemax, aria-valuenow, aria-label, tabindexThe 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.