@hello-pangea/dnd Documentation
repository·main·Indexed 26 days ago
https://github.com/hello-pangea/dndA high-level, accessible, and performant drag-and-drop API optimized for React list interactions. It supports vertical, horizontal, and nested lists, offering strong keyboard and screen reader support. The library is built around three core components: <DragDropContext />, <Droppable />, and <Draggable />, and emphasizes a 'physicality' design principle to simulate natural object movement.
What's inside @hello-pangea/dnd
- @hello-pangea/dnd is a library providing beautiful and accessible drag and drop for lists in React. It is designed for vertical and horizontal lists, movement between lists, and nested lists. It emphasizes natural movement, high performance, and strong accessibility (keyboard and screen reader support).
Accessibility features in @hello-pangea/dnd
main@hello-pangea/dnd provides comprehensive accessibility support to ensure drag and drop interactions are available to users who do not use a mouse or touch interface. Key features include:
- Full keyboard support: Enables reordering, combining, and moving items between lists using only a keyboard.
- Keyboard multi-drag support: Allows selecting and moving multiple items via keyboard.
- Keyboard auto-scrolling: Enables scrolling while interacting via keyboard.
- Screen reader support: Includes built-in English messaging for screen reader announcements out of the box.
- Browser focus management: Smart handling of focus states during drag operations.
- Lighthouse compliance: The library is tested against Google Lighthouse to maintain high accessibility scores.
Understand screen reader support in @hello-pangea/dnd
main@hello-pangea/dndprovides built-in screen reader support in English out of the box. The experience is primarily focused on keyboard interactions.Key accessibility implementation details:
- It does not use the HTML5 drag and drop API.
- It does not use the deprecated
aria-grabbedoraria-dropeffectattributes. - It uses ARIA live regions to provide real-time state updates (drag lifecycle announcements) to screen reader users.
Understand the physicality design principle in @hello-pangea/dnd
mainThe core design philosophy of
@hello-pangea/dndis physicality. The library aims to simulate the movement of physical objects rather than digital UI elements. This manifests in several ways:- No instant movement (no snapping): Items are never instantly moved. Instead, items animate out of the way during a drag, and items animate into their new positions upon drop.
- Movement-based positioning: Instead of using drop shadows, lines, or clones to indicate a drop location, the library uses the natural movement of items to communicate where a dragged item will land.
- Center of gravity logic: Dragging impact is determined by the item's center of gravity rather than the initial grab point. A list item is considered 'dragged over' when the center position of the dragging item crosses a boundary, and resting items move out of the way once the dragging item's center passes their edge.
- Natural cross-list movement: Keyboard movement between lists is driven by simulated inertia, gravity, and collisions rather than simple index-based logic.
Explore community projects and examples
mainThe following community projects are based on
react-beautiful-dndand may have compatibility issues with future versions of@hello-pangea/dnd. They serve as inspiration or reference for implementing drag-and-drop features:- Kanban Boards:
- kanban-dnd: A Kanban style to-do list with custom lane creation and reordering.
- Simple Trello: A Trello clone using the React ecosystem (Demo).
- react-kanban: A customizable Kanban/Trello board library.
- Games:
- Testing Utilities:
- react-beautiful-dnd-test-utils: Test utilities built with
react-testing-library.
- react-beautiful-dnd-test-utils: Test utilities built with
- Kanban Boards:
Understand how items move out of the way during dragging
mainTo maximize performance, items that move out of the way of a dragging item use CSS transitions instead of physics, allowing the GPU to handle the movement.
The animation curve is composed of three distinct phases:
- Warm up period: Mimics natural response time.
- Quick phase: Rapidly moves the item out of the way.
- Long tail: A slower final phase to ensure text remains readable while animating.
Customize drag starting announcements
mainTo customize the message read when a user lifts a
<Draggable />(e.g., via the spacebar), use theonDragStartresponder in<DragDropContext />.Best Practices:
- Use Position, not Index: Instead of saying "index 1", say "position 2". Use
position = index + 1. - Include Context: Since the API doesn't automatically know the list size (especially in virtual lists), you should manually include the list length and list name in your custom message for a better experience.
Default Message: "You have lifted an item in position `${startPosition}."
- Use Position, not Index: Instead of saying "index 1", say "position 2". Use
Reparent a `<Draggable />` using the Cloning API
mainWhen a parent element has a CSS
transformapplied, theposition: fixedused during dragging may result in incorrect positioning. To fix this, you can use the Cloning API to move the dragging item to a different DOM location (likedocument.body).When using the Cloning API, the original
<Draggable />is removed during the drag, and a clone is rendered into a container specified bygetContainerForClone(defaults todocument.body).Note: Using the Cloning API is required for compatibility with virtual lists. For best results, ensure the clone is the same size as the original item to prevent displacement issues.
function List(props) { const items = props.items; return ( <Droppable droppableId="droppable" renderClone={(provided, snapshot, rubric) => ( <div {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} > Item id: {items[rubric.source.index].id} </div > )} > {(provided) => ( <div ref={provided.innerRef} {...provided.droppableProps}> {items.map((item) => ( <Draggable draggableId={item.id} index={item.index}> {(provided, snapshot) => ( <div {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} > Item id: {item.id} </div > )} </Draggable> ))} </div > )} </Droppable> ); }Understand Auto-Scrolling constraints and behaviors
mainContainer Definition
A container is defined as either a
<Droppable />that is scrollable, a<Droppable />that has a scroll parent, or thewindowitself.Scrolling Constraints
- Large Draggables: If a
<Draggable />is larger than the container on the axis you are attempting to scroll, auto-scrolling on that specific axis will be disabled. For example, a<Draggable />taller than the window height will prevent vertical auto-scrolling, but horizontal auto-scrolling will still function. - Input Types: Auto-scrolling works for mouse, touch, and keyboard inputs. For keyboard users, the library manages a combination of
<Droppable />scrolling,windowscrolling, and manual movements to ensure items reach their intended positions. - iOS Limitation: On iOS (Webkit) browsers, users may experience a noticeable shaking effect during auto-scrolling due to a known Webkit bug.
- Large Draggables: If a
Implement fixed layout strategy for table reordering
mainThe Fixed Layout strategy is faster and simpler but requires that column widths do not change based on content. You can achieve this using
table-layout: fixedor by manually setting specific widths (e.g.,50%) on cells.Implementation Options:
- Dynamic Display: Set
display: tableon the<Draggable />row specifically while it is being dragged. - Permanent Widths (Recommended): To avoid styling issues during dragging, set a permanent width on each
<td>using inline styles or CSS (e.g.,width: 100px). This avoids the need for event responders.
- Dynamic Display: Set
Use natural drag animations with natural-drag-animation-rbdnd
mainThe
natural-drag-animation-rbdndaddon adds natural dragging animations to your drag-and-drop implementation.⚠️ Warning: This addon is based on
react-beautiful-dndand might not work with future versions of@hello-pangea/dnd.https://github.com/rokborf/natural-drag-animation-rbdndReorder table rows with reparenting (Portals/Cloning)
mainWhen using reparenting (such as
ReactDOM.createPortalor cloning) with table row reordering, you must handle the fact that moving a<tr>involves unmounting the old row and mounting a new one in the portal. This unmounting causes the loss of cell dimension information.To preserve cell dimensions during reparenting:
- In the
componentWillUnmountlifecycle of the<tr>, read the current widths of the cells from the DOM and store these values in a location accessible outside the component. - In the
componentDidMountlifecycle of the new mounting<tr>, check ifDraggableStateSnapshot.isDraggingis true. - If a previously recorded width exists, apply those stored widths to the new cells via inline styles.
- In the