Skip to main content

StackedSegments

N-pane resizable vertical layout with draggable horizontal dividers. Stack any number of panes and get smooth drag-to-resize separators, keyboard support (↑↓), and configurable min-height clamping.

Quick start
import { StackedSegments } from "mates-ui";

StackedSegments(
  [html`<p>Top</p>`, html`<p>Bottom</p>`],
  { defaultHeights: ["50%", "auto"] },
);

Two panes — equal split

import { html, renderApp } from 'mates';
import { StackedSegments } from 'mates-ui';

const App = () => () => html`
<div style="height:340px;width:100%;max-width:40rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${StackedSegments([
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Top pane</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">Drag the divider to resize.</p></div>`,
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Bottom pane</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">Both panes start at equal heights.</p></div>`,
], { defaultHeights: ["50%", "auto"] })}
</div>
`;

renderApp(App, document.getElementById('app'));

Three panes — custom heights

import { html, renderApp } from 'mates';
import { StackedSegments } from 'mates-ui';

const App = () => () => html`
<div style="height:400px;width:100%;max-width:40rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${StackedSegments([
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Toolbar</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">120 px starting height.</p></div>`,
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Content</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">30% starting height.</p></div>`,
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Output</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">&quot;auto&quot; — remaining space.</p></div>`,
], { defaultHeights: ["120px", "30%", "auto"] })}
</div>
`;

renderApp(App, document.getElementById('app'));

No handle

import { html, renderApp } from 'mates';
import { StackedSegments } from 'mates-ui';

const App = () => () => html`
<div style="height:280px;width:100%;max-width:40rem;border:1px solid var(--md-color-border);border-radius:var(--md-radius-md);overflow:hidden;">
${StackedSegments([
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Top pane</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">No grip dots — plain divider line.</p></div>`,
html`<div style="padding:var(--md-space-4);width:100%;box-sizing:border-box;"><p style="margin:0;font-weight:600;font-size:var(--md-text-sm);">Bottom pane</p><p style="margin:var(--md-space-2) 0 0;font-size:var(--md-text-sm);color:var(--md-color-text-muted);">Clean divider, no affordance.</p></div>`,
], { showHandle: false })}
</div>
`;

renderApp(App, document.getElementById('app'));

StackedSegments

import { StackedSegments } from "mates-ui";
Prop Type Default Description
ariaLabel string "Resizable panes" Accessible label for the container's role="group".
attr AttrMap HTML attributes on the root element.
children* TemplateResult[] Array of pane contents. One child renders a plain container; N children render N−1 draggable horizontal dividers.
classes string | string[] Extra root classes — joined and appended after built-in classes.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
defaultHeights string[] [] Per-pane initial heights. Supports px ("120px"), percent ("30%"), or "auto" (equal share). Shorter arrays are padded with "auto".
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
minPaneHeight string "48px" Minimum resize floor for every pane.
on OnEventMap DOM event handlers on the root element.
paneLabel string "Pane {n}" Per-pane aria-label template; `{n}` is replaced with the 1-based pane number.
resizePaneLabel string "Resize pane {n}" Separator aria-label template; `{n}` is replaced with the pane number.
showHandle boolean true Show the 3-dot grip handle on each divider.
step number 8 Keyboard ArrowUp / ArrowDown step size in pixels.
style StyleMap Inline CSS on the root element.

CSS tokens

Token Role
--md-color-border Divider resting color.
--md-color-primary Divider hover / drag / focus color.
--md-color-text-muted Grip handle dot color.

Caveats

  • Each pane must manage its own overflow — add `overflow: auto` to content inside panes as needed.
  • The last pane always fills remaining height (`flex: 1 1 0`) and cannot be directly resized — drag the separator above it.
  • `defaultHeights` are resolved once on mount relative to the container's pixel height.