OPERATOR

SplitPane

Horizontal workspace

Primary panel: 60%

Telemetry

Drag the separator, press its arrow buttons, or focus it and use the arrow keys. Below 640px, both panels stack.

Inspector

Panel content keeps its reading order as the workspace narrows.

primary precedes secondary in reading order. size is the primary panel percentage, excluding the handle. Use defaultSize for local state or size / onSizeChange for controlled state.

Drag the separator, focus it and use the arrow keys, or press the two arrow buttons in the middle of the divider. Each press moves the split 10 points (or step, when that is larger) within minSize and maxSize; a button at its limit is unavailable. The buttons are the alternative for anyone who can point and tap but not drag.

<SplitPane
  primary={<Telemetry />}
  secondary={<Inspector />}
  defaultSize={60}
  minSize={25}
  maxSize={75}
  label="Resize telemetry and inspector"
/>

Vertical workspace

orientation="vertical" places primary above secondary. Give the container a bounded height.

Primary panel: 60%

Telemetry

Drag the separator, press its arrow buttons, or focus it and use the arrow keys.

Inspector

Panel content keeps its reading order as the workspace narrows.

Disabled

Primary panel: 60%

Telemetry

Drag the separator, press its arrow buttons, or focus it and use the arrow keys. Below 640px, both panels stack.

Inspector

Panel content keeps its reading order as the workspace narrows.

Responsive behavior

Below stackBelow (default 640 CSS pixels of the split pane's own width), a horizontal split stacks its panels and the handle leaves the focus order. The saved split returns at wider widths. Stacked inside a bounded height, the panels share it by the saved split, each scrolling inside itself; with no bounded height the stack grows to its content. A vertical split never stacks: its panels are already above and below. Set stackBelow={0} to keep resizing available.

Scrolling panels

A panel whose content is larger than the panel scrolls inside it, so the page never widens. While it scrolls it is a named region in the tab order, called primaryLabel and secondaryLabel (by default "Primary panel" and "Secondary panel"); a panel that fits is neither.

Bounds clamp to 0–100%; maxSize cannot fall below minSize. Non-finite values use defaults; invalid steps use 1%. Keep bounds away from 0 and 100 to keep both panes visible.

Props

PROPTYPEDEFAULTREQUIRED
primary

Primary panel content, always first in reading order.

node—yes
secondary

Secondary panel content.

node—yes
size

Controlled primary-panel percentage of available space, excluding the handle. Clamped to minSize/maxSize. Stacked panels share a bounded height by the same split.

number—no
defaultSize

Initial uncontrolled primary-panel percentage.

number50no
onSizeChange

(percent: number) => void. Called for valid keyboard, pointer or step-button adjustments.

function—no
minSize

Minimum primary-panel percentage. Finite values clamp to 0–100; non-finite values use 20.

number20no
maxSize

Maximum primary-panel percentage. Finite values clamp between minSize and 100; non-finite values use 80.

number80no
step

Positive percentage increment for the arrow keys and dragging. Invalid values use 1. The step buttons move 10 points, or step when it is larger.

number1no
orientation

Horizontal puts panels side by side; vertical puts them above and below. Give vertical layouts a bounded height.

enum
horizontal | vertical
"horizontal"no
disabled

Turns resizing off: the separator leaves the focus order and the step buttons are hidden.

booleanfalseno
label

Accessible name of the separator.

string"Resize panels"no
stackBelow

Container width in CSS pixels below which a horizontal split stacks its panels and hides resizing. A vertical split never stacks. Use 0 to disable responsive stacking.

number640no
primaryLabel

Accessible name of the primary panel while its content scrolls; it is then a focusable region.

string"Primary panel"no
secondaryLabel

Accessible name of the secondary panel while its content scrolls; it is then a focusable region.

string"Secondary panel"no

Accessibility

  • The focusable separator announces its label, limits, and primary-panel percentage, and is described by its keyboard instructions.
  • Left/Right resizes horizontal layouts; Up/Down resizes vertical layouts. Home/End selects min/max.
  • The separator has a 24px target and visible focus. The step buttons are 24px targets named "Move divider left" and "Move divider right" ("up" and "down" when vertical); they stay out of the Tab order because the keyboard resizes from the separator.
  • If the separator has focus when the panels stack, focus moves to the primary panel instead of the page.
  • A scrolling panel is a named region a keyboard user can reach and scroll.
  • Disabled resizing hides the step buttons and leaves panel content and controls usable.

Manifest entry

<SplitPane primary={<section>Telemetry</section>} secondary={<section>Inspector</section>} defaultSize={60} label="Resize telemetry and inspector" />
View agent manifest
{
  "description": "Two panels with a keyboard and pointer resize separator and two step buttons on the divider; a horizontal split stacks below a container-width threshold.",
  "props": {
    "primary": {
      "type": "node",
      "required": true,
      "description": "Primary panel content, always first in reading order."
    },
    "secondary": {
      "type": "node",
      "required": true,
      "description": "Secondary panel content."
    },
    "size": {
      "type": "number",
      "description": "Controlled primary-panel percentage of available space, excluding the handle. Clamped to minSize/maxSize. Stacked panels share a bounded height by the same split."
    },
    "defaultSize": {
      "type": "number",
      "default": 50,
      "description": "Initial uncontrolled primary-panel percentage."
    },
    "onSizeChange": {
      "type": "function",
      "description": "(percent: number) => void. Called for valid keyboard, pointer or step-button adjustments."
    },
    "minSize": {
      "type": "number",
      "default": 20,
      "description": "Minimum primary-panel percentage. Finite values clamp to 0–100; non-finite values use 20."
    },
    "maxSize": {
      "type": "number",
      "default": 80,
      "description": "Maximum primary-panel percentage. Finite values clamp between minSize and 100; non-finite values use 80."
    },
    "step": {
      "type": "number",
      "default": 1,
      "description": "Positive percentage increment for the arrow keys and dragging. Invalid values use 1. The step buttons move 10 points, or step when it is larger."
    },
    "orientation": {
      "type": "enum",
      "values": [
        "horizontal",
        "vertical"
      ],
      "default": "horizontal",
      "description": "Horizontal puts panels side by side; vertical puts them above and below. Give vertical layouts a bounded height."
    },
    "disabled": {
      "type": "boolean",
      "default": false,
      "description": "Turns resizing off: the separator leaves the focus order and the step buttons are hidden."
    },
    "label": {
      "type": "string",
      "default": "Resize panels",
      "description": "Accessible name of the separator."
    },
    "stackBelow": {
      "type": "number",
      "default": 640,
      "description": "Container width in CSS pixels below which a horizontal split stacks its panels and hides resizing. A vertical split never stacks. Use 0 to disable responsive stacking."
    },
    "primaryLabel": {
      "type": "string",
      "default": "Primary panel",
      "description": "Accessible name of the primary panel while its content scrolls; it is then a focusable region."
    },
    "secondaryLabel": {
      "type": "string",
      "default": "Secondary panel",
      "description": "Accessible name of the secondary panel while its content scrolls; it is then a focusable region."
    }
  },
  "composable_with": [
    "Card",
    "DataGrid",
    "Chart",
    "EventTimeline"
  ],
  "never_inside": [
    "Button"
  ],
  "example": "<SplitPane primary={<section>Telemetry</section>} secondary={<section>Inspector</section>} defaultSize={60} label=\"Resize telemetry and inspector\" />"
}

On this page