Skip to content

NbDragHandle is the six-dot grip you press to move a block, a table row or a table column. It is a real button with a name, a visible focus ring and a keyboard path, and it reports a drag without performing one: where the item may land, what the drop indicator looks like and how the data reorders all belong to the host.

Use it when the reorder logic is yours, as in a document editor or a data table. For a plain vertical list, NbReorderList already owns the whole job.

Tab to a handle, press Space, move with the arrow keys, and press Space again to drop.

vue
<template>
  <div v-for="(block, index) in blocks" :key="block.id" class="block">
    <NbDragHandle
      :label="`Move ${block.label}`"
      :announcement="announcement"
      @drag-start="onStart(index, $event)"
      @drag-move="onMove(index, $event)"
      @drag-end="onDrop"
      @drag-cancel="onCancel"
    />
    {{ block.label }}
  </div>
</template>

The drag lifecycle ​

Every way of dragging reports the same four events, each with an IDragHandleEvent whose via says what drove it.

EventPointerKeyboardNative (native)
drag-startPointer travelled past thresholdSpace or Enterdragstart
drag-moveEvery pointer moveEach arrow key along axis(use your drop target)
drag-endPointer releasedSpace / Enter, or focus leftdragend with a drop
drag-cancelEscape or pointercancelEscapedragend without a drop

For a pointer, deltaX and deltaY are pixels from where the press started, and clientX / clientY are the pointer. For the keyboard they count arrow-key steps since pick-up, and direction names the step just taken. A host that reorders live can act on direction. One that previews and commits on drop can use the deltas.

A press that never travels past threshold stays a plain click, so a handle can also open a block menu on click. A press that did become a drag does not also fire click.

Native drag and drop ​

Editors built on ProseMirror move blocks with the browser's own drag and drop, and need to fill dataTransfer themselves. Set native and the handle becomes draggable and forwards the DragEvent on drag-start:

vue
<NbDragHandle
  native
  :label="`Move ${block.label}`"
  @drag-start="({ event }) => startBlockDrag(event as DragEvent)"
/>

The keyboard path is the same in both modes.

Axis ​

axis picks the glyph and the arrow keys. A handle for a row or a block uses vertical (the default), a column handle uses horizontal, and a free-floating item uses both.

Announcing moves ​

A keyboard user does not see the item travel. The handle cannot know the new position, because it does not own the reorder, so it gives you a polite live region instead: set announcement after each move ("Introduction moved to position 3 of 5") and it is read out.

Hosts that move DOM nodes ​

A browser drops focus from a node when that node is moved, which is exactly what a keyed list does when the host reorders on drag-move. The handle recognises that blur (focus went nowhere and nothing was clicked) and takes focus back, so the item stays held. A blur to another control, or a click elsewhere, drops the item, the same as NbReorderList.

Accessibility ​

  • A native <button type="button">, named by the required label. The name gains ", picked up" while held.
  • The keyboard instructions are the button's description (aria-describedby), so they are heard once on focus rather than repeated as part of the name. Override instructions to translate them.
  • grab at rest, grabbing for the length of a pointer drag. The grabbing cursor is set on the document, so it holds wherever the pointer goes, and text selection is suppressed while dragging.
  • touch-action: none, so a touch drag moves the item instead of scrolling the page.
  • Focus is shown with the standard focus ring. A held item is outlined in the primary colour.

Props ​

PropTypeDefaultDescription
labelstring(required)Accessible name. Name the thing being moved.
axis'vertical' | 'horizontal' | 'both''vertical'Glyph and arrow keys.
sizenumber16Glyph size in pixels.
thresholdnumber4Pixels a pointer must travel before a press becomes a drag.
nativebooleanfalseUse HTML drag and drop and forward the DragEvent.
instructionsstringEnglish keyboard helpDescription read on focus.
announcementstring''Contents of the polite live region.
disabledbooleanfalseRenders the handle inert and cancels a drag in progress.

Events ​

EventPayloadDescription
drag-startIDragHandleEventA drag began.
drag-moveIDragHandleEventThe pointer moved, or an arrow key was pressed while held.
drag-endIDragHandleEventThe item was dropped.
drag-cancelIDragHandleEventThe drag was abandoned. Undo any preview.
ts
interface IDragHandleEvent {
  via: 'pointer' | 'keyboard' | 'native'
  clientX?: number
  clientY?: number
  deltaX: number
  deltaY: number
  direction?: 'up' | 'down' | 'left' | 'right'
  event: Event
}

Exposed ​

MemberTypeDescription
cancel() => voidCancels a pointer or keyboard drag.
draggingRef<boolean>A pointer or native drag is in progress.
grabbedRef<boolean>A keyboard user is holding the item.
elRef<HTMLButtonElement | null>The button.

Tokens used ​

TokenApplied to
--nb-c-text-subtle / --nb-c-textGlyph at rest / on hover and focus
--nb-c-surface-hoverHover background
--nb-c-primaryHeld outline and glyph
--nb-c-focus-ringFocus outline
--nb-c-component-disabledDisabled glyph