Splitter
hc-splitter splits two panes with a movable handle — the shadcn
Resizable equivalent. It follows the WAI-ARIA Window Splitter
pattern: the handle is a focusable role="separator" whose
aria-valuenow tracks the primary pane’s size. installSplitter wires
the pointer drag, keyboard resize, and ARIA; the layout is plain
flexbox driven by a single --hc-splitter-pos custom property.
Also known as: split pane, resizable panes.
Basic HTML
Section titled “Basic HTML”Give the container a cross-axis size (a height for the default horizontal layout).
<div class="hc-splitter" data-orientation="horizontal" data-value="40" style="block-size:10rem"> <div class="hc-splitter__panel">Primary pane</div> <div class="hc-splitter__handle" role="separator" tabindex="0" aria-label="Resize panels"></div> <div class="hc-splitter__panel">Secondary pane</div></div>import { installSplitter } from '@hypermedia-components/core';installSplitter(); // idempotent; returns an uninstallerThe zero-config @hypermedia-components/core/behaviors entry installs
it automatically and picks up content swapped in by htmx.
Vertical
Section titled “Vertical”data-orientation="vertical" stacks the panes with a horizontal handle.
<div class="hc-splitter" data-orientation="vertical" data-value="40" style="block-size:14rem"> <div class="hc-splitter__panel">Top</div> <div class="hc-splitter__handle" role="separator" tabindex="0" aria-label="Resize panels"></div> <div class="hc-splitter__panel">Bottom</div></div>Configuration
Section titled “Configuration”| Attribute | Purpose |
|---|---|
data-orientation | horizontal (default, side-by-side) or vertical (stacked). |
data-value | Initial primary-pane size, percent (default 50). |
data-min / data-max | Allowed range, percent (default 10 / 90). |
data-step | Keyboard step, percent (default 5). |
data-collapsible | Double-click / Enter on the handle toggles the primary pane collapsed ↔ last-open. |
data-persist | localStorage key — saves and restores the position (a persisted 0 restores collapsed). |
Keyboard
Section titled “Keyboard”Tab to the handle, then:
| Key | Action |
|---|---|
← / → (horizontal) or ↑ / ↓ (vertical) | Resize by one step |
Home | Minimum size |
End | Maximum size |
Enter | Toggle collapse (when data-collapsible) |
DOM focus is on the handle; aria-valuenow / aria-valuemin /
aria-valuemax describe the primary pane’s size to assistive tech.
The change event
Section titled “The change event”Each resize dispatches a bubbling hc:splitterchange on the container:
splitter.addEventListener('hc:splitterchange', (e) => { const { value, collapsed, orientation } = e.detail; // value: primary-pane %});collapsed is true while the primary pane is folded away (see below).
Collapse
Section titled “Collapse”Add data-collapsible so the handle can fold the primary pane away and
bring it back. Double-click the handle, or focus it and press
Enter, to toggle between collapsed (0%) and the last open size.
<div class="hc-splitter" data-orientation="horizontal" data-value="40" data-collapsible style="block-size:10rem"> <div class="hc-splitter__panel">Primary pane</div> <div class="hc-splitter__handle" role="separator" tabindex="0" aria-label="Resize panels"></div> <div class="hc-splitter__panel">Secondary pane</div></div>A collapsed splitter carries data-collapsed on the container — style the
panes off it — and hc:splitterchange reports detail.collapsed. The
handle’s aria-valuenow stays within [min, max]; the collapsed state
rides on data-collapsed rather than an out-of-range value.
Persistence
Section titled “Persistence”Add data-persist="<key>" to mirror the position into localStorage and
restore it on the next visit. A persisted 0 restores the collapsed state.
Storage failures (private mode, disabled) degrade silently to the
data-value default.
<div class="hc-splitter" data-orientation="horizontal" data-collapsible data-persist="editor:sidebar" style="block-size:10rem"> …</div>Hyperscript
Section titled “Hyperscript”data-persist covers the common localStorage case; reach for Hyperscript
when you need custom storage (a cookie, a server round-trip, a different
key shape):
<div class="hc-splitter" data-orientation="horizontal" _="on hc:splitterchange call myStore.set('split', event.detail.value)"> …</div>More patterns: Hyperscript → Reacting to component events.
Collapsing a pane
Section titled “Collapsing a pane”A pane can collapse to its own content — a rail holding the control that brings it back:
<div class="hc-splitter__panel" data-collapsed> <button class="hc-button" data-size="sm" type="button">Reasons (5)</button></div>data-collapsed gives the pane its content’s size, hands the freed
space to its sibling, and hides the drag handle (there is nothing to
drag). The state belongs to whoever owns the pane’s content, so a
server re-rendering that region can set it on the fragment it already
sends — no client state, and the two panes cannot disagree.
Collapse to a rail, not to nothing. A panel that disappears when closed is a dead end: the user who closed it has no way to find it again.
Accessibility
Section titled “Accessibility”- The handle is
role="separator"+tabindex="0"witharia-valuenow/aria-valuemin/aria-valuemaxandaria-controlspointing at the primary pane (per the APG Window Splitter pattern). - The separator’s
aria-orientationis set automatically (a side-by-side split uses averticalseparator line, and vice-versa). - Give the handle an accessible name with
aria-labeloraria-labelledby(the primary pane’s heading).
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
splitter.handle-size | Handle thickness (the hit area). |
splitter.handle-bg / handle-hover-bg | Handle background (hover / focus tints with data-color). |
splitter.grip-color / grip-active-color | The centred grip bar. |
splitter.grip-length / grip-thickness | Grip dimensions. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-splitter-handle-size | -handle-bg | -handle-hover-bg--hc-splitter-grip-color | -grip-active-color | -grip-length | -grip-thickness--hc-splitter-pos (runtime: the primary pane's size, set by installSplitter)Out of scope
Section titled “Out of scope”Three-or-more panes, nested splitters, and pixel-based minimums are deferred.