Skip to content

Drag & Drop

trud-calendar uses the Pointer Events API for all drag interactions — working seamlessly on mouse, touch screens, and stylus devices with zero extra dependencies.

Set enableDnD={true} and provide handlers:

<Calendar
events={events}
enableDnD
onEventDrop={(event, newStart, newEnd) => {
await api.updateEvent(event.id, { start: newStart, end: newEnd });
}}
onEventResize={(event, newStart, newEnd) => {
await api.updateEvent(event.id, { start: newStart, end: newEnd });
}}
onSlotSelect={(start, end) => {
// User dragged across empty slots to select a time range
openCreateModal({ start, end });
}}
/>
  • Month view: Drag an event pill from one day cell to another. The date changes but the original time is preserved.
  • Week/Day view: Drag an event from one time slot to another. Both date and time change, snapping to 15-minute increments.
  • Event duration is always preserved.
  • In Week/Day view, drag the bottom edge of an event to change its duration.
  • Snaps to 15-minute increments.
  • Fires onEventResize with the updated start/end times.
  • Drag across empty time slots in Week/Day view to select a time range.
  • Fires onSlotSelect(start, end) with the selected range.
  • Great for “create event” flows — the user drags to select when, then fills in the details.

All interactions work on touch devices out of the box:

  • Touch and hold an event, then drag to move
  • Touch the resize handle at the bottom of an event and drag to resize
  • Touch and drag across empty slots to select a time range

The calendar uses touch-action: none on interactive elements to prevent the browser from scrolling during drag operations. A 5px movement threshold distinguishes taps from drags.

onEventDrop?: (
event: CalendarEvent, // The original event object
newStart: DateTimeString, // "2026-03-15T10:00:00"
newEnd: DateTimeString, // "2026-03-15T11:00:00"
) => void;
onEventResize?: (
event: CalendarEvent, // The original event object
newStart: DateTimeString, // "2026-03-15T10:00:00"
newEnd: DateTimeString, // "2026-03-15T11:30:00" (new duration)
) => void;
onSlotSelect?: (
start: DateTimeString, // "2026-03-15T10:00:00"
end: DateTimeString, // "2026-03-15T11:00:00"
) => void;
function App() {
const [events, setEvents] = useState(initialEvents);
const [creating, setCreating] = useState(null);
const handleDrop = async (event, newStart, newEnd) => {
// Optimistic update
setEvents((prev) =>
prev.map((e) =>
e.id === event.id ? { ...e, start: newStart, end: newEnd } : e
)
);
await fetch(`/api/events/${event.id}`, {
method: "PATCH",
body: JSON.stringify({ start: newStart, end: newEnd }),
});
};
return (
<Calendar
events={events}
enableDnD
onEventDrop={handleDrop}
onEventResize={handleDrop}
onSlotSelect={(start, end) => setCreating({ start, end })}
/>
);
}