Snap to Objects
Snaplines draw alignment guides while you move or resize an element, pulling its edges and centre into line when they land within a few pixels of a neighbour. They keep a hand-arranged diagram tidy without a layout algorithm. Mount <Snaplines> inside a <Paper> and the guides turn on for every move and resize.
<Snaplines> attaches to the <Paper> it is rendered in, so mount it as a child of one.
Aligning elements
Drag any card in the board below. Guides appear as its edges or centre line up with a neighbour, and letting go within the snap range clicks it into place. The toolbar exposes the three things worth tuning: whether snaplines are on, how close counts as aligned, and whether a card snaps to every card or only to others in its group.
Each toolbar control maps to one prop: the snap range is distance, the group restriction is isSnapTarget, and the on/off switch mounts or unmounts the component.
Snap distance
distance sets how close, in pixels, an edge or centre has to get before it snaps. It defaults to 10. Raise it and elements snap from farther off, easier to trigger but less precise. Lower it and an element snaps only once it is nearly aligned. The demo's 5, 10, and 30 let you compare a tight snap with a loose one.
<Snaplines distance={20} />
Choosing what snaps
By default a dragged element can snap to any other element on the paper. isSnapTarget narrows that down. It runs once per candidate and returns whether that candidate is a valid target for the element being dragged; since it gets both, you can compare them. The demo keeps snapping within a card's own group:
<Snaplines
isSnapTarget={({ model, targetModel }) =>
model.get('data')?.group === targetModel.get('data')?.group
}
/>
Return true to keep a target, false to skip it.
Where isSnapTarget filters the targets, canSnap gates the dragged element itself. Return false and that element ignores snaplines as it moves. It still drags freely and lands wherever you drop it, without being pulled into alignment. Use it to exempt specific elements from snapping:
<Snaplines canSnap={({ model }) => !model.get('data')?.freeMove} />
Snapping to the grid
usePaperGrid ties snapping to the paper's grid, but only as a refinement of an existing alignment. Once an element lines up with another, its snapped position is rounded to the paper's gridSize, so it lands both aligned and on the grid. With no alignment within distance, nothing rounds. It is off by default.
<Snaplines usePaperGrid />
Extra snap points
additionalSnapPoints adds snap positions beyond the default element edges and centres, useful for guides like a page centre or a fixed margin. It runs for the element being moved or resized and returns the extra points. The callback reports the gesture as either 'move' or 'resize', so the points can change with it:
<Snaplines additionalSnapPoints={() => [{ x: 400, y: 0 }]} />
Turning snaplines on and off
Snaplines are active whenever <Snaplines> is mounted. To turn them off, stop rendering it; mount it again to turn them back on. The demo's first toggle does exactly that.
{snapEnabled && <Snaplines />}
Styling the guides
The guides are dashed lines drawn from CSS custom properties, so you can recolour or resize them in your own stylesheet without touching the component. Override the variables on .jj-snaplines, or anywhere above it such as :root:
.jj-snaplines {
--jj-snaplines-color: #0891b2; /* guide colour */
--jj-snaplines-width: 1px; /* line thickness (a length, e.g. 1px) */
--jj-snaplines-dash: 6; /* dash length, a unitless multiplier of 1px */
--jj-snaplines-gap: 4; /* gap between dashes, a unitless multiplier of 1px */
}
The same variables style both the horizontal and vertical guides.
A <Stencil> on the same paper picks up a mounted <Snaplines> on its own, so shapes snap as you drag them in from the palette.