demo · v137

Shadow selection explorer

Three blocks — one plain DOM, one open shadow root, one closed shadow root. Drag-select across boundaries: the left lane uses legacy getRangeAt(0) and goes blind at the shadow boundary; the right lane uses getComposedRanges() and surfaces every selected fragment plus the resolved direction.

probing…

Plain paragraph. Try dragging from here down into the cards below — the cards live in shadow trees.

Between two shadow hosts; selecting through this paragraph also works.

Closing paragraph. Backwards selection from here demonstrates the new direction property — legacy anchor/focus couldn’t reliably tell you which end was the “start”.

legacy: getRangeAt(0)

what the old API sees

no selection
v137: getComposedRanges()

composed view + direction

direction: — ranges: 0
no selection

the code

document.addEventListener("selectionchange", () => {
  const sel = getSelection();
  // OLD: getRangeAt(0) stops at the first shadow boundary
  const oldRange = sel.rangeCount ? sel.getRangeAt(0) : null;

  // NEW (Chrome 137+): every range, across closed/open shadow trees
  const ranges = sel.getComposedRanges({ shadowRoots: [openHost.shadowRoot, closedHost.shadowRoot] });
  const direction = sel.direction; // "forward" | "backward" | "none"
});

see also