Documentation
Concepts

Keyboard and accessibility

Moving and resizing items without a pointer, what the grid tells you at each step, and the names and words that are yours.

Every item is a tab stop (its drag handle, when it has one).

keywhat it does
Space or Entergrabs the item, or drops the grabbed one
Arrowsmove the grabbed item a cell
Shift + arrowsresize it from its end and bottom edges
Escapeputs it back where it was
Tabputs it back, and moves the focus on

The arrows follow what they show: in right-to-left, ArrowRight moves toward the inline start. A step a middleware would refuse is not taken. A drop commits one command.

Bringing items in

A drag source is a tab stop too. Enter or Space on it brings its item into the grid at the first free cell, already grabbed; the same keys place it, and Enter adds it and moves the focus to it. See External drop.

Replacing a key

Your onKeyDown on Root runs first: preventDefault cancels or replaces any of these keys.

What is said

The primitives set no role, no name and no text. The grid tells you each step (grab, move, resize, drop, cancel) through useGridLayoutEvents; say it in a live region in your own words. A step of an item coming in from outside carries external: true.

announcer.tsx
function Announcer({ onMessage }: { onMessage: (message: string) => void }) {
    useGridLayoutEvents((event) => {
        if (event.type === "drop") onMessage(`Moved to column ${event.item.x + 1}, row ${event.item.y + 1}.`);
    });
    return null;
}

Give each item a name (aria-label) and describe what it is (aria-roledescription), and tell people the keys (aria-describedby).

Keyboard
Gallery