shadcn.io is not affiliated with official shadcn/ui
Drag Schedule Calendar Block for React Aria
A day of time blocks you can drag, or lift and move with the keyboard, in 30-minute steps, with refused overlaps, an add form, Remove and every move announced.
Installation
Overview
Slide a day's blocks of time around with this drag schedule for React with React Aria. Wednesday 14 October 2026 is a list of half-hour rows from 07:00 to 19:00 in a scrolling region, with seven blocks laid at their time and length. Drag a block by its grip with a pointer and it snaps to 30 minutes, or press Space on the grip to lift it, use the arrow keys to move it, press Space to drop it and Escape to cancel. Move earlier and move later Buttons on every block do the same job alone. A drop that would land on another block is refused and the status line names the block it would overlap. Above the list, a title Input, a start time, a 30, 60 or 90 minute ToggleGroup and the one primary ShineButton add a block, pressing an empty row sets the start, and Remove deletes a block. Every move is announced in a polite status line. It's built from Button, Field, Input, NativeSelect, ToggleGroup and Guides, with no drag library.
FAQ
How do I fill it with my blocks?
Edit BLOCKS. Each has an id, a start in minutes after midnight, a len in minutes and a title. OPEN, CLOSE and STEP set the hours and the snap. The positions, the overlap check and the status text are all worked out from that array.
How does the dragging work?
The grip starts a pointer drag that the column keeps, so the block can move while it is dragged. The pointer's position is turned into a start time and snapped to 30 minutes, and the list scrolls when you drag near its edge. When you let go the drop is checked for an overlap and refused with a reason, or kept.
How do I move a block with the keyboard?
Tab to the grip and press Space to lift the block, press the arrow keys to move it by 30 minutes, then Space to drop it or Escape to cancel. Each step is announced. If you drop it on another block it stays lifted and says which one is in the way. The earlier and later buttons move a block one step without lifting it.
Why does it refuse an overlap and not push the other block?
A time block is a promise, so moving one should never silently move another. The refusal says which block is in the way and where yours stays. If you want a pushing behaviour, change commit to shift the blocks below it.