はじめに
Reactでタスクリストの並べ替えやカンバンボードを作ろうとして、ドラッグ&ドロップの実装で手が止まった経験はありませんか。マウスイベントを自前で組むと、ドラッグ中の要素の追従、ドロップ先の判定、キーボード操作への対応まで考えることが一気に増えます。
React-Beautiful-Dndは、こうしたリスト系のドラッグ&ドロップを「美しく、アクセシブルに」実装するためにAtlassianが開発したライブラリです。DragDropContext・Droppable・Draggableという3つのコンポーネントを組み合わせるだけで、マウスはもちろんキーボードやスクリーンリーダーにも対応した並べ替えUIが作れます。
ただし正直にお伝えすると、このライブラリは2023年に非推奨(deprecated)となり、2025年8月にはGitHubリポジトリもアーカイブされています。それでも設計が完成度高く、既存プロダクトでの採用実績も多いため、今から学んでも十分に価値があります。この記事では現状を踏まえたうえで、使い方と代替手段の両方を紹介します。
まずは実際に動かしてみたほうが早いと思います。手を動かしながら確認したい方はこちらからどうぞ。
特徴・メリット
アクセシブルなドラッグ&ドロップ
React-Beautiful-Dndの最大の特徴は、マウスドラッグだけでなくキーボード操作(スペースキーでつかむ、矢印キーで移動)にも標準対応している点です。スクリーンリーダー向けの読み上げテキストも組み込まれており、アクセシビリティを後付けで悩まなくて済みます。
自然なアニメーション
要素をドラッグすると、周囲のアイテムが物理演算のような滑らかさで場所を譲り合います。この動きを自分で実装しようとすると地味に大変なので、標準で入っているのはありがたいポイントです。
大量データでも高速
公式ドキュメントでは、仮想化されたリストであれば10,000件のアイテムでも60fpsで動作するとされています。パフォーマンスを気にせず大きなリストに適用できます。
現状は非推奨・アーカイブ済み
一方で、npm上ではdeprecated扱いとなっており、GitHubリポジトリも2025年8月18日付でアーカイブされ、読み取り専用になっています。作者のAlex Reardon氏(Atlassian)は、後継として低レベルなAPIを提供する「Pragmatic drag and drop」への移行を案内しています。新規プロジェクトではそちらの採用も検討する価値があります。
また、React 18のStrictModeとは相性が悪く、開発モードでのエフェクト二重実行によってドラッグ可能な要素を見失うという既知の問題があります。回避するにはStrictModeを外すか、この問題を修正したコミュニティフォーク「@hello-pangea/dnd」への切り替えが定番の対処法です。
インストール方法
npmでインストールします。
npm install react-beautiful-dnd
yarnやpnpmを使う場合は次のとおりです。
yarn add react-beautiful-dnd
pnpm add react-beautiful-dnd
peerDependenciesはreact・react-domともに^16.8.5 || ^17.0.0 || ^18.0.0です。React 19には対応していないため、既存プロジェクトで導入する際はReactのバージョンを確認してください。
React-Beautiful-Dndのサンプルを動かす
タスクの並べ替えができるシンプルなリストです。DragDropContextでドラッグ&ドロップ全体を囲み、Droppableがドロップ可能な領域、Draggableが個々のドラッグ可能な要素を表します。ドラッグを離すとonDragEndが呼ばれ、移動元(source)と移動先(destination)のインデックスが渡されます。
要点だけを抜き出すとこうなります。
function App() {
const [tasks, setTasks] = useState(initialTasks)
const onDragEnd = ({ source, destination }) => {
if (!destination) return // リスト外にドロップされたら何もしない
const next = Array.from(tasks)
const [moved] = next.splice(source.index, 1)
next.splice(destination.index, 0, moved)
setTasks(next)
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="tasks">
{(provided) => (
<ul ref={provided.innerRef} {...provided.droppableProps}>
{tasks.map((task, index) => (
<Draggable key={task.id} draggableId={task.id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
{task.text}
</li>
)}
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
)
}
実際に動かせるものが下です。カードをドラッグして順番を入れ替えてみてください。並べ替えるたびにonDragEndから渡される新しい順番がコンソールに出力されます。
Draggableの子要素に渡ってくるsnapshot.isDraggingを使うと、ドラッグ中だけ背景色を変えるといった見た目の演出ができます。initialTasksの項目を増やしたり、onDragEndの中身をAPI呼び出しに差し替えたりすれば、そのまま実務のTodoリストに応用できます。
基本的な使い方
React-Beautiful-Dndの基本は、常に3つのコンポーネントの組み合わせです。
DragDropContext- ドラッグ&ドロップ全体を管理するコンテキスト。onDragEndで結果を受け取りますDroppable- アイテムをドロップできる領域。droppableIdで識別しますDraggable- ドラッグ可能な個々の要素。draggableIdとindexが必須です
onDragEndに渡されるresultオブジェクトには、移動元のsource.droppableId・source.indexと、移動先のdestination.droppableId・destination.indexが含まれます。同じリスト内の並べ替えならdroppableIdは同じ値になり、異なるリスト間の移動ならここが変わります。この違いを使い分けることで、単純な並べ替えから複数リスト間の移動まで同じAPIで実装できます。
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
<DragDropContext onDragEnd={(result) => console.log(result)}>
<Droppable droppableId="list">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{/* Draggableをmapで並べる */}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
provided.placeholderを忘れると、ドラッグ中にリストの高さが縮んでガタつくので必ず配置してください。
実践的なユースケース
カンバンボード形式(複数リスト間の移動)
タスク管理ツールでおなじみの、列をまたいでカードを移動できるカンバンボードです。Droppableを複数並べ、onDragEndの中でsource.droppableIdとdestination.droppableIdを比較することで、同じ列内の並べ替えと、別の列への移動を1つの関数で処理しています。
const onDragEnd = ({ source, destination }) => {
if (!destination) return
const sourceItems = Array.from(columns[source.droppableId].items)
const [moved] = sourceItems.splice(source.index, 1)
if (source.droppableId === destination.droppableId) {
sourceItems.splice(destination.index, 0, moved)
// 同じ列内での並べ替え
} else {
const destItems = Array.from(columns[destination.droppableId].items)
destItems.splice(destination.index, 0, moved)
// 別の列への移動
}
}
Droppableのsnapshot.isDraggingOverを使うと、カードがドラッグされて重なっている列の背景色を変えられます。列を増やしたい場合はinitialColumnsにキーを追加するだけで対応できます。
横並びのドラッグ&ドロップ(タグの並べ替え)
Droppableにdirection="horizontal"を渡すと、縦方向の並べ替えと同じDraggableのコードのまま、横方向のドラッグ&ドロップに切り替わります。タグや優先度バッジの並べ替えUIによく使うパターンです。
<Droppable droppableId="tags" direction="horizontal">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps} style={{ display: 'flex' }}>
{/* Draggableを横に並べる */}
{provided.placeholder}
</div>
)}
</Droppable>
directionプロパティをverticalに戻せば元の縦並びに戻ります。タグの表示順をそのままAPIに保存するようにすれば、ユーザーごとの並び順を記憶させる機能にもそのまま使えます。
ドラッグ中の見た目カスタマイズ(isDragDisabled・isDragging)
DraggableにはisDragDisabledという条件付きでドラッグを禁止するプロパティがあります。特定の項目だけ並べ替えを禁止したい場合(進行中のタスクを固定するなど)に便利です。あわせてsnapshot.isDraggingでドラッグ中の影や背景色を変え、操作している要素を視覚的に強調しています。
<Draggable draggableId={item.id} index={index} isDragDisabled={isLockedItem}>
{(provided, snapshot) => (
<li
style={{
boxShadow: snapshot.isDragging ? '0 8px 16px rgba(0,0,0,0.15)' : 'none',
cursor: isLockedItem ? 'not-allowed' : 'grab',
}}
>
{item.text}
</li>
)}
</Draggable>
チェックボックスで「リリース作業」だけドラッグを禁止できます。チェックを入れた状態でその項目をつかもうとすると、ドラッグが発生しないことを確認してみてください。
同じisDragDisabledの仕組みは、権限のないユーザーに特定の行だけ触らせたくない場合や、承認済みのタスクを固定したい場合にもそのまま応用できます。
まとめ
React-Beautiful-Dndは、DragDropContext・Droppable・Draggableという3つのコンポーネントだけで、キーボード操作にも対応したアクセシブルな並べ替えUIを実装できるライブラリです。単純なリストの並べ替えから、カンバンボードのような複数リスト間の移動、横並びのタグ整理、ドラッグ禁止のような細かい制御まで、同じonDragEndベースの設計で一貫して書けるのが強みでした。
一方で、2023年の非推奨化とGitHubリポジトリのアーカイブという事実は無視できません。React 18のStrictModeとの相性問題も残ったままです。既存プロジェクトの保守であればこのまま使い続けるか@hello-pangea/dndフォークへの切り替えを検討し、新規プロジェクトであれば後継の「Pragmatic drag and drop」も選択肢に入れて比較してみることをおすすめします。とはいえ、リスト系ドラッグ&ドロップのAPI設計としては今も学ぶ価値のあるお手本だと感じています。