grid

grid

Snap movement to a grid pattern

The grid plugin snaps draggable movement to a grid pattern. Instead of smooth, continuous movement, elements jump between grid points - perfect for alignment, layout tools, or pixel-perfect positioning.

grid([20, 20]); // 20px square grid
grid([50, 25]); // 50px wide, 25px tall rectangles
grid([10, null]); // Only snap horizontally every 10px
grid([null, 15]); // Only snap vertically every 15px

Basic Usage

<script setup>
import { grid, vDraggable } from '@neodrag/vue';
</script>

<template>
  <div>
    <div v-draggable="[grid([20, 20])]">Snaps to 20px squares</div>
    <div v-draggable="[grid([50, 30])]">
      Snaps to 50x30px rectangles
    </div>
    <div v-draggable="[grid([25, null])]">
      Only snaps horizontally
    </div>
    <div v-draggable="[grid([null, 20])]">Only snaps vertically</div>
  </div>
</template>

Dynamic Grid Sizing

<script setup>
import { ref } from 'vue';
import { grid, vDraggable, useCompartment } from '@neodrag/vue';

const gridSize = ref(20);
const enableSnap = ref(true);

const gridComp = useCompartment(() =>
  enableSnap.value
    ? grid([gridSize.value, gridSize.value])
    : grid([null, null]),
);
</script>

<template>
  <div>
    <div v-draggable="() => [gridComp]">
      Grid: {{ enableSnap ? `${gridSize}px` : 'disabled' }}
    </div>

    <label>
      Grid Size: {{ gridSize }}px
      <input
        v-model.number="gridSize"
        type="range"
        min="5"
        max="50"
        step="5"
      />
    </label>

    <label>
      <input v-model="enableSnap" type="checkbox" />
      Enable Grid Snap
    </label>
  </div>
</template>

Advanced Grid Patterns

Asymmetric Grids

<script setup>
import { grid, vDraggable } from '@neodrag/vue';
</script>

<template>
  <div>
    <div v-draggable="[grid([100, 20])]">100x20px grid</div>
    <div v-draggable="[grid([60, null])]">
      60px intervals (hourly)
    </div>
    <div v-draggable="[grid([80, 120])]">Daily x Weekly</div>
  </div>
</template>

Responsive Grid System

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { grid, vDraggable, useCompartment } from '@neodrag/vue';

const screenWidth = ref(window.innerWidth);

// 12-column responsive grid
const columnWidth = computed(() => screenWidth.value / 12);
const responsiveGrid = useCompartment(() =>
  grid([columnWidth.value, 40]),
);

function handleResize() {
  screenWidth.value = window.innerWidth;
}

onMounted(() => {
  window.addEventListener('resize', handleResize);
});

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
});
</script>

<template>
  <div v-draggable="() => [responsiveGrid]">
    Responsive 12-column grid
    <br />
    Column: {{ Math.round(columnWidth) }}px
  </div>
</template>

Real-World Examples

Pixel Art Editor

// Precise 1px grid for pixel-perfect editing
const pixelGrid = [grid([1, 1]), bounds(BoundsFrom.element(canvas))];

Card Layout System

// Cards snap to a 12-column grid system
const containerWidth = 1200;
const columnWidth = containerWidth / 12;
const cardGrid = [
  grid([columnWidth, 40]), // 12-column, 40px row height
  bounds(BoundsFrom.parent()),
];

Timeline Editor

// Timeline with 15-minute intervals
const minutesPerPixel = 0.25; // 15 minutes = 60 pixels
const timelineGrid = [
  grid([60, null]), // 15-minute intervals horizontally
  bounds(BoundsFrom.element(timelineContainer)),
];

Icon Grid Layout

// Desktop-style icon grid
const iconSize = 64;
const iconSpacing = 80;
const iconGrid = [
  grid([iconSpacing, iconSpacing]),
  bounds(BoundsFrom.viewport({ top: 100, bottom: 50 })),
];

How It Works

The grid plugin modifies movement by snapping to grid points:

  1. Calculate grid position - Uses Math.ceil(value / gridSize) * gridSize to find the next grid point
  2. Apply to proposed movement - Modifies ctx.proposed.x and ctx.proposed.y
  3. Null values skip snapping - null means free movement on that axis
  4. Runs during drag - Continuously snaps while dragging

The snapping formula ensures elements always move forward to the next grid intersection, creating predictable, aligned positioning.

API Reference

function grid(size: [number | null, number | null]): Plugin;

Parameters:

  • size - Array with [x, y] grid dimensions
    • number - Grid size in pixels for that axis
    • null - No grid snapping for that axis

Usage patterns:

  • grid([20, 20]) - Square 20px grid
  • grid([50, 30]) - Rectangular 50x30px grid
  • grid([25, null]) - Horizontal snapping only
  • grid([null, 15]) - Vertical snapping only
  • grid([null, null]) - No snapping (effectively disabled)

Returns: A plugin object for use with draggable.