Skip to main content

DragHandle

MD3 drag handle for resizing panes (Compose VerticalDragHandle). It is a vertical capsule placed between two side-by-side panes. Drag it sideways, or focus it and use the arrow keys, to change how the width is split, for example between the list and the detail pane of a list-detail layout.

  • Ali ConnorsBrunch this weekend?
  • Sandra AdamsSummer BBQ
  • Trevor HansenOui oui
Detail pane
const [width, setWidth] = useState(280)

<div style={{ display: 'flex' }}>
<section id="list-pane" aria-label="Inbox" style={{ flex: `0 0 ${width}px` }}>…</section>
<DragHandle
aria-label="Inbox"
aria-controls="list-pane"
min={200}
max={480}
value={width}
onChange={(event, value) => setWidth(value)}
style={{ alignSelf: 'center' }}
/>
<section style={{ flex: 1 }}>…</section>
</div>

Size and layout​

The handle only reports a size. Your layout applies it, usually as the width of the pane next to the handle.

  • value / defaultValue is the size of the controlled pane in px, kept between min and max. onChange(event, value) fires on every pointer move and on every key press that changes the value.
  • paneSide says which side of the handle that pane is on. With "start" (the default, like the list in a list-detail layout), dragging toward the inline end makes it bigger. With "end", dragging toward the inline start makes it bigger. Both follow the writing direction, so they flip in RTL.
  • onDragStart and onDragEnd fire once per pointer drag. Use onDragEnd to save the final size, for example to localStorage.
  • The handle is 24dp wide (the spacing between panes) and has a 48dp touch target.

States​

At rest the handle is a 4×48dp capsule in the outline color. While it is pressed or dragged it becomes a 12×52dp on-surface shape with 12dp corners. Like Compose, it switches between the two without animation. Hover, focus and press state layers and the focus ring are drawn on the capsule.

Accessibility​

The handle is a WAI-ARIA window splitter:

  • It has role="separator", aria-orientation="vertical", and aria-valuenow, aria-valuemin and aria-valuemax set from value, min and max. getAriaValueText adds a readable aria-valuetext, for example "280 pixels".
  • Give it a name that matches the pane it resizes (aria-label or aria-labelledby), and point aria-controls at that pane.
  • Keyboard: ← and → move the handle by step (default 1), or by shiftStep (default 10) with Shift. Page Up and Page Down also change the size by shiftStep, and Home and End jump to min and max. ↑ makes the pane bigger and ↓ makes it smaller.
  • disabled sets aria-disabled, removes the handle from the tab order and ignores both pointer and keyboard input.

Props​

PropTypeDefaultDescription
defaultValuenumberminUncontrolled initial size.
disabledbooleanfalseDisables dragging and keyboard resizing and removes the handle from the tab order.
getAriaValueText((value: number) => string)—Human-readable size for assistive tech (aria-valuetext), e.g. (v) => \${v} pixels\``.
maxnumber100Largest size (aria-valuemax).
minnumber0Smallest size (aria-valuemin).
onChange((event: SyntheticEvent<Element, Event>, value: number) => void)—Fires while dragging and on each key press, with the triggering event and the new (clamped) size. Not fired when the size would not change.
onDragEnd((event: PointerEvent<HTMLDivElement>, value: number) => void)—Fires when a pointer drag ends (release or cancel), with the final size — e.g. to persist it.
onDragStart((event: PointerEvent<HTMLDivElement>, value: number) => void)—Fires when a pointer drag starts (after the first movement), with the size at that point.
paneSide"start" | "end"startWhich side of the handle the controlled pane is on (logical, so it mirrors in RTL). 'start': dragging toward the inline end grows it (the list of a list-detail layout); 'end': dragging toward the inline start grows it.
shiftStepnumber10Size change per Shift + arrow and Page Up / Page Down.
stepnumber1Size change per arrow key.
valuenumber—Controlled size of the controlled pane, in px (the separator's aria-valuenow).