Swapy

A framework-agnostic tool that converts any layout into a drag-to-swap one with just a few lines of code.

Default

4.875

Project Views

last year

New Users

57K

+10%

We Build Future of

Design Industry

Team of passionate designers and developers

Daily New clients

54+40%

UI-Layouts

Font

SK-Modernist

Smart Digital

Agency For Your

Business

Trusted By

500+ Users

Don't Take Our Words For It...

Cards balance

$ 12,457

Card HolderExpires
Robert Fox07/22

Handle

⚡ Turbocharge Everything

🧠 Brainy Decisions

🌈 Magical Moments

🚀 Innovate Like Crazy

💰 Save Big, Smile More

🔮 AI Vibes Only

Opacity

Streamline Operations

Decision Making

Customer Experiences

Accelerate Innovation

Reduce Costs

Future-Proof

Raws

A
B
C
D
E
F
G
H
I

Installation

npm install swapy
swapy.tsx
1
"use client"
2
3
import type React from "react"
4
5
import { useEffect, useRef } from "react"
6
import { createSwapy, type SlotItemMapArray } from "swapy"
7
import { cn } from "@/lib/utils"
8
9
type AnimationType = "dynamic" | "spring" | "none"
10
type SwapMode = "hover" | "drop"
11
12
type Config = {
13
animation: AnimationType
14
continuousMode: boolean
15
manualSwap: boolean
16
swapMode: SwapMode
17
autoScrollOnDrag: boolean
18
}
19
20
type SwapyLayoutProps = {
21
id: string
22
enable?: boolean
23
onSwap?: (event: { newSlotItemMap: { asArray: SlotItemMapArray } }) => void
24
config?: Partial<Config>
25
className?: string
26
children: React.ReactNode
27
}
28
29
export const SwapyLayout = ({ id, onSwap, config = {}, className, children }: SwapyLayoutProps) => {
30
const containerRef = useRef<HTMLDivElement | null>(null)
31
const swapyRef = useRef<ReturnType<typeof createSwapy> | null>(null)
32
33
useEffect(() => {
34
const container = containerRef.current
35
if (!container) return
36
37
swapyRef.current = createSwapy(container, config)
38
39
if (onSwap) {
40
swapyRef.current.onSwap(onSwap)
41
}
42
43
return () => {
44
swapyRef.current?.destroy()
45
}
46
}, [config, onSwap])
47
48
return (
49
<div id={id} ref={containerRef} className={className}>
50
{children}
51
</div>
52
)
53
}
54
55
export const DragHandle = ({className}:{className?:string}) => {
56
return (
57
<div
58
data-swapy-handle
59
className={cn("absolute top-2 left-2 cursor-grab text-gray-500 rounded-md active:cursor-grabbing dark:border-gray-700 dark:bg-gray-800",className)}
60
>
61
<svg
62
xmlns="http://www.w3.org/2000/svg"
63
width="24"
64
height="24"
65
viewBox="0 0 24 24"
66
fill="none"
67
stroke="currentColor"
68
stroke-width="2"
69
stroke-linecap="round"
70
stroke-linejoin="round"
71
className="lucide lucide-grip-vertical-icon lucide-grip-vertical opacity-80"
72
>
73
<circle cx="9" cy="12" r="1" />
74
<circle cx="9" cy="5" r="1" />
75
<circle cx="9" cy="19" r="1" />
76
<circle cx="15" cy="12" r="1" />
77
<circle cx="15" cy="5" r="1" />
78
<circle cx="15" cy="19" r="1" />
79
</svg>
80
</div>
81
);
82
};
83
84
export const SwapySlot = ({
85
id,
86
className,
87
children,
88
}: {
89
id: string
90
className?: string
91
children: React.ReactNode
92
}) => {
93
return (
94
<div className={cn("data-[swapy-highlighted]:bg-neutral-200 data-[swapy-highlighted]:dark:bg-neutral-800", className)} data-swapy-slot={id}>
95
{children}
96
</div>
97
)
98
}
99
100
const dragOpacityClassMap: Record<number, string> = {
101
10: "data-[swapy-dragging]:opacity-10",
102
20: "data-[swapy-dragging]:opacity-20",
103
30: "data-[swapy-dragging]:opacity-30",
104
40: "data-[swapy-dragging]:opacity-40",
105
50: "data-[swapy-dragging]:opacity-50",
106
60: "data-[swapy-dragging]:opacity-60",
107
70: "data-[swapy-dragging]:opacity-70",
108
80: "data-[swapy-dragging]:opacity-80",
109
90: "data-[swapy-dragging]:opacity-90",
110
100: "data-[swapy-dragging]:opacity-100",
111
};
112
113
export const SwapyItem = ({
114
id,
115
className,
116
children,
117
dragItemOpacity = 100, // default to 100
118
}: {
119
id: string;
120
className?: string;
121
children: React.ReactNode;
122
dragItemOpacity?: number;
123
}) => {
124
const opacityClass = dragOpacityClassMap[dragItemOpacity] ?? "data-[swapy-dragging]:opacity-50";
125
return (
126
<div
127
className={cn(
128
opacityClass,
129
className
130
)}
131
data-swapy-item={id}
132
>
133
{children}
134
</div>
135
);
136
};

Structure

1
<SwapyLayout id="swapy" >
2
<SwapySlot id="slot-1">
3
<SwapyItem id="item-1">
4
<DragHandle />
5
Item 1
6
</SwapyItem>
7
<SwapyItem id="item-2">
8
<DragHandle />
9
Item 2
10
</SwapyItem>
11
<SwapyItem id="item-3">
12
<DragHandle />
13
Item 3
14
</SwapyItem>
15
</SwapySlot>
16
</SwapyLayout>

Props

SwapyLayout Props

PropsTypeDefaultDescription
idstringA unique identifier for the layout container.
enablebooleanWhether to enable drag-and-drop (currently unused in logic).
onSwap(event: { newSlotItemMap: { asArray: SlotItemMapArray } }) => voidCallback fired when an item is swapped.
configPartial<{ animation: "dynamic" | "spring" | "none"; continuousMode: boolean; manualSwap: boolean; swapMode: "hover" | "drop"; autoScrollOnDrag: boolean; }>{}Configuration object for swapy behavior.
classNamestringAdditional class names to apply to the layout container.
childrenReact.ReactNodeChild components (usually SwapySlots containing SwapyItems).

SwapyItem Props

PropTypeDefaultDescription
idstringUnique identifier for the draggable item.
classNamestringAdditional class names.
childrenReact.ReactNodeContent to render inside the draggable item.
dragItemOpacitynumber (10–100)100Tailwind opacity value applied when item is being dragged (e.g., 50 = 50%).

SwapySlot Props

PropTypeDefaultDescription
idstringUnique identifier for the slot (drop target).
classNamestringAdditional class names.
childrenReact.ReactNodeContent to render inside the slot.

DragHandle Props

PropTypeDefaultDescription
classNamestringOptional class names for the handle.