dnd-kit
Add drag-and-drop functionality with @dnd-kit. Use whenever the user wants to reorder items, sort a list, build a kanban board, or any drag-to-rearrange interaction. For reordering/sorting use dnd-kit; for free-drag/swipe gestures (drag a card around, swipe-to-dismiss) use the animations skill.
- License 许可
- AGPL-3.0
- 最近更新
- 2026-11-05
- Stars
- ★ 42
- Forks
- 9
01 前言 Front Matter
- name
- dnd-kit
- description
- Add drag-and-drop functionality with @dnd-kit. Use whenever the user wants to reorder items, sort a list, build a kanban board, or any drag-to-rearrange interaction. For reordering/sorting use dnd-kit; for free-drag/swipe gestures (drag a card around, swipe-to-dismiss) use the animations skill.
03 SKILL.md 正文
dnd-kit — Drag and Drop
Sortable list (most common pattern)
import { DndContext, closestCenter, type DragEndEvent } from "@dnd-kit/core"
import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { GripVertical } from "lucide-react"
interface Item { id: string; title: string }
function SortableItem({ item }: { item: Item }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item.id })
const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1 }
return (
<div ref={setNodeRef} style={style} className="flex items-center gap-2 rounded-lg border bg-card p-3">
<button {...attributes} {...listeners} className="cursor-grab active:cursor-grabbing text-muted-foreground">
<GripVertical className="h-4 w-4" />
</button>
<span>{item.title}</span>
</div>
)
}
function SortableList() {
const [items, setItems] = useState<Item[]>([
{ id: "1", title: "First" },
{ id: "2", title: "Second" },
{ id: "3", title: "Third" },
])
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
if (over && active.id !== over.id) {
setItems((prev) => {
const oldIndex = prev.findIndex((i) => i.id === active.id)
const newIndex = prev.findIndex((i) => i.id === over.id)
return arrayMove(prev, oldIndex, newIndex)
})
}
}
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<div className="space-y-2">
{items.map((item) => <SortableItem key={item.id} item={item} />)}
</div>
</SortableContext>
</DndContext>
)
}
Kanban board (multiple columns)
Columns are droppable zones (useDroppable); cards are the SortableItem from above. over.id on drop is either a column id (dropped on empty space) or a card id (dropped on another card) — resolve both to a column, then move the card.
import { DndContext, type DragEndEvent, DragOverlay, type DragStartEvent, useDroppable } from "@dnd-kit/core"
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"
type ColumnId = "todo" | "doing" | "done"
type Columns = Record<ColumnId, Item[]>
const COLUMN_TITLES: Record<ColumnId, string> = { todo: "To Do", doing: "In Progress", done: "Done" }
function Column({ id, items }: { id: ColumnId; items: Item[] }) {
const { setNodeRef, isOver } = useDroppable({ id })
return (
<div className="flex-1">
<h3 className="mb-2 text-sm font-medium text-muted-foreground">{COLUMN_TITLES[id]}</h3>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<div
ref={setNodeRef}
className={cn("min-h-24 space-y-2 rounded-lg border p-2", isOver && "bg-muted/50")}
>
{items.map((item) => <SortableItem key={item.id} item={item} />)}
</div>
</SortableContext>
</div>
)
}
function KanbanBoard() {
const [columns, setColumns] = useState<Columns>({
todo: [{ id: "1", title: "First" }, { id: "2", title: "Second" }],
doing: [{ id: "3", title: "Third" }],
done: [],
})
const [activeId, setActiveId] = useState<string | null>(null)
const columnIds = Object.keys(columns) as ColumnId[]
const findColumn = (id: string): ColumnId | undefined =>
columnIds.find((col) => columns[col].some((item) => item.id === id))
const findItem = (id: string): Item | undefined =>
columnIds.flatMap((col) => columns[col]).find((item) => item.id === id)
function handleDragStart(event: DragStartEvent) {
setActiveId(event.active.id as string)
}
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event
setActiveId(null)
if (!over) return
const activeItemId = active.id as string
const overId = over.id as string
const from = findColumn(activeItemId)
const to = columnIds.includes(overId as ColumnId) ? (overId as ColumnId) : findColumn(overId)
if (!from || !to || from === to) return
setColumns((prev) => {
const item = prev[from].find((i) => i.id === activeItemId)
if (!item) return prev
return {
...prev,
[from]: prev[from].filter((i) => i.id !== activeItemId),
[to]: [...prev[to], item],
}
})
}
const activeItem = activeId ? findItem(activeId) : undefined
return (
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<div className="flex gap-4">
{columnIds.map((id) => <Column key={id} id={id} items={columns[id]} />)}
</div>
<DragOverlay>{activeItem ? <SortableItem item={activeItem} /> : null}</DragOverlay>
</DndContext>
)
}
This moves a card only between different columns; to also reorder within a column, add an arrayMove branch when from === to (see the sortable list above). useDroppable, cn, and useState are imported from @dnd-kit/core, @/lib/utils, and react respectively.
Key rules
- Every draggable item needs a unique string
id - Use
arrayMovefrom@dnd-kit/sortableto reorder arrays useSortablehook providesattributes,listeners,setNodeRef,transform,transition- Apply
CSS.Transform.toString(transform)as inline style - Use
DragOverlayfor a floating preview during drag
04 同仓库更多 Skills
figma-use
**MANDATORY prerequisite** — you MUST invoke this skill BEFORE every `use_figma` tool call. NEVER call `use_figma` directly without loading this skill first. Skipping it causes common, hard-to-debug failures. Trigger whenever the user wants to perform a write action or a unique read action that requires JavaScript execution in the Figma file context — e.g. create/edit/delete nodes, set up variables or tokens, build components and variants, modify auto-layout or fills, bind variables to properties, or inspect file structure programmatically.
figma-use-slides
This skill helps agents use Figma's use_figma MCP tool in the Slides context. Can be used alongside figma-use which has foundational context for using the use_figma tool.
figma-use-motion
Motion / animation context for the `use_figma` MCP tool — animating Figma nodes via manual keyframes, animation styles, easing, and timeline duration. Load alongside figma-use whenever a task involves adding, editing, or inspecting animation on a node.
figma-use-figjam
This skill helps agents use Figma's use_figma MCP tool in the FigJam context. Can be used alongside figma-use which has foundational context for using the use_figma tool.
figma-swiftui
SwiftUI ↔ Figma translation. Use whenever the user mentions Swift, SwiftUI, iOS, iPhone, or iPad — in EITHER direction — translating a Figma design into SwiftUI (design → code), or pushing SwiftUI views / screens / tokens back into a Figma file (code → design). Triggers on phrases like 'implement this Figma design in SwiftUI', 'build this screen in Swift', 'push this SwiftUI view to Figma', 'mirror my Swift code in a Figma file', or whenever a Figma URL appears alongside `.swift` files / an `.xcodeproj`. Routes to a direction-specific reference doc; loads alongside `figma-use` for the code → design path.
figma-shaders
**MANDATORY prerequisite** — load this skill before calling `create_shader` or `update_shader`. Use when the user asks to create, author, change, fix, or iterate on a shader effect, shader fill, custom effect, custom fill, or procedural shader in Figma.