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.
Enable drag and drop
Section titled “Enable drag and drop”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 }); }}/>Interactions
Section titled “Interactions”Event move
Section titled “Event move”- 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 the configured increment.
- Event duration is always preserved.
Event resize
Section titled “Event resize”- In Week/Day view, drag the top edge of an event to change its start time, or the bottom edge to change its end time.
- Snaps to the configured increment (default 15 minutes).
- Fires
onEventResizewith the updated start/end times.
Drag-to-create (slot selection)
Section titled “Drag-to-create (slot selection)”- 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.
Background events
Section titled “Background events”Use display: "background" on events to render them as colored time blocks behind regular events. Great for showing business hours, availability, or blocked time:
const events = [ { id: "business-hours", title: "Business Hours", start: "2026-03-25T08:00:00", end: "2026-03-25T18:00:00", display: "background", color: "#22c55e", }, // ... regular events];
<Calendar events={events} />Background events are non-interactive — they cannot be dragged, resized, or clicked.
Auto-scroll
Section titled “Auto-scroll”When dragging, resizing, or selecting near the top or bottom edge of the time grid, the container automatically scrolls to reveal more content. No configuration needed — it activates during any pointer interaction.
Touch support
Section titled “Touch support”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.
Long press delay
Section titled “Long press delay”On touch devices, you may want to require a long press before drag starts, to avoid interfering with scrolling:
<Calendar events={events} enableDnD longPressDelay={300} // 300ms hold before drag activates on touch onEventDrop={handleDrop}/>This only affects touch interactions — mouse and stylus dragging remains immediate.
Callbacks
Section titled “Callbacks”onEventDrop
Section titled “onEventDrop”onEventDrop?: ( event: CalendarEvent, // The original event object newStart: DateTimeString, // "2026-03-15T10:00:00" newEnd: DateTimeString, // "2026-03-15T11:00:00") => void;onEventResize
Section titled “onEventResize”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
Section titled “onSlotSelect”onSlotSelect?: ( start: DateTimeString, // "2026-03-15T10:00:00" end: DateTimeString, // "2026-03-15T11:00:00") => void;Snap duration
Section titled “Snap duration”By default, all interactions snap to 15-minute increments. Change it with snapDuration:
<Calendar events={events} enableDnD snapDuration={30} // Snap to 30-minute increments onEventDrop={handleDrop} onEventResize={handleResize}/>Common values: 5, 10, 15, 30, 60.
Constraints
Section titled “Constraints”Control where events can be dropped, resized, or selected by providing constraint callbacks. Return false to prevent the action:
<Calendar events={events} enableDnD // Only allow drops within business hours dragConstraint={(event, newStart, newEnd) => { const hour = new Date(newStart).getHours(); return hour >= 8 && hour < 18; }} // Prevent resizing past 2 hours resizeConstraint={(event, newStart, newEnd) => { const ms = new Date(newEnd).getTime() - new Date(newStart).getTime(); return ms <= 2 * 60 * 60 * 1000; }} // Only allow selecting within business hours selectConstraint={(start, end) => { const hour = new Date(start).getHours(); return hour >= 8 && hour < 18; }} onEventDrop={handleDrop} onEventResize={handleResize} onSlotSelect={handleSelect}/>When a constraint returns false, the interaction is silently reverted — the event returns to its original position and no callback fires.
Example: Full interaction handler
Section titled “Example: Full interaction handler”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 })} /> );}