gridla

Pixel-precise grids and nested layouts, solved as plain data.

A framework-neutral engine that moves, resizes, places, and transfers items in pixel coordinates, projects a layout onto any canvas size, and flattens trees of nested layouts. Zero runtime dependencies; adapters for React, Vue, Svelte, Solid, Angular, Qwik, and Web Components included.

npm install gridla
  • zero runtime dependencies
  • core about 23 kB min+gzip
  • MIT licensed
projectLayout · chainmoveItem · push-x

One core, one adapter per framework

The core is a set of pure functions. Adapters are optional.

Solve in the core with plain objects, or let an adapter own measurement, gestures, and previews while you own the state. Same names, same data attributes, in every framework.

import { moveItem, type GridLayout } from 'gridla'

const layout: GridLayout = {
  canvas: {
    width: 960,
    height: 600,
    padding: { top: 0, right: 0, bottom: 0, left: 0 },
    heightMode: 'bounded',
  },
  items: [
    { id: 'chart', x: 0, y: 0, w: 640, h: 360 },
    { id: 'note', x: 660, y: 0, w: 300, h: 360 },
  ],
}

const result = moveItem({ layout, itemId: 'note', position: { x: 0, y: 0 }, options: { gap: 12 } })
result.strategy // 'push-x' — chart slid right to make room
result.layout.items[0] // { id: 'chart', x: 312, ... } — note sits at x=0; the input layout is untouched

Try it here

Real canvases, not screenshots.

Each box below is GridProvider, GridCanvas, and GridItem with a few lines of CSS. Pointer, touch, and keyboard all work.

Push or swap

strategyidle

Drag a card. Nudge into a neighbor to push it aside; cover it to swap. Every commit names the strategy that produced it.

Responsive projection

100%
canvas600×300

Slide to narrow the canvas. Rows behave like flex chains and gaps stay exact; the navigation column is fixed-w and keeps its 140 pixels.

Nested groups

A group is a layout inside an item: one provider per container. Drag the cards inside the group, or drag the group by its bar and push the side column.

What it does

Grounded in the solver, not in adjectives.

  • Intent-driven move solver

    moveItem infers intent from overlap: push along an axis, swap, reorder a row or column, insert into a lane, trim a large neighbor, snap to an open slot, or shrink a chain. Every result names the strategy that produced it.

    • push-x
    • swap
    • reorder-row
    • insert-column
    • trim-neighbor
    • fit-open-slot
  • Resize, place, transfer

    resizeItem snaps the dragged edge and shrinks only the neighbors it collides with. placeItem inserts by top-left intent or centered on a pointer. transferItem moves an item between canvases and rescales it.

  • Projection that keeps structure

    projectLayout re-fits a layout to a new canvas: rows and columns behave like flex chains, fixed items keep their pixels, gaps stay exact, free space scales.

  • Nested layouts as math

    flattenLayout turns a tree of layouts into root-relative rectangles, with hit testing, container lookup, locked subtrees, and coordinate conversion.

  • Constraints and policies

    Per-item minW/maxH, four size modes, collision ignore ghosts, and locked walls that never move as a side effect.

  • Headless adapters

    GridProvider, GridCanvas, and GridItem handle measurement, pointer and keyboard gestures, previews, and cross-canvas transfer, in React, Vue, Svelte, Solid, Angular, Qwik, Web Components, or plain DOM. Appearance stays yours.