Pixel-precise grids and nested layouts that move, resize, snap, and reflow.
A framework-neutral engine with adapters for React, Vue, Svelte, Solid, Angular, Qwik, Web Components, and the DOM.
Gridla turns "the user dragged this box to (412, 96)" into a complete, valid layout. It pushes, swaps, reorders, or shrinks neighbors to make room, keeps fixed sizes and configured gaps intact, projects layouts onto any canvas size, and does all of it with plain objects. No DOM in the core, no state library, no drag-and-drop framework, zero runtime dependencies.
npm install gridlaimport { createItem, moveItem, projectLayout } from 'gridla'
const layout = {
canvas: {
width: 960,
height: 600,
padding: { top: 0, right: 0, bottom: 0, left: 0 },
heightMode: 'bounded',
},
items: [
createItem('chart', { w: 480, h: 300, minW: 160 }, 0, 0),
createItem('sidebar', { w: 480, h: 300, minW: 160 }, 480, 0),
createItem('table', { w: 960, h: 300 }, 0, 300),
],
}
// Ask for a move. Get back a whole layout and the strategy that produced it.
const result = moveItem({
layout,
itemId: 'chart',
position: { x: 480, y: 0 },
options: { gap: 12 },
})
result.accepted // true
result.strategy // 'push-x' — the sidebar slid left to make room
result.layout.items.find((item) => item.id === 'sidebar')?.x // 0
// Fit the same layout into a narrower canvas. Rows stay rows, gaps stay 12px.
const narrow = projectLayout(result.layout, { width: 640, height: 400 }, { gap: 12 })With React, the adapter owns measurement, pointer capture, keyboard nudging, previews, and commits. You own the layout state.
import { GridProvider, GridCanvas, GridItem } from 'gridla/react'
function Dashboard({ layout, setLayout }) {
return (
<GridProvider layout={layout} onLayoutChange={setLayout} gap={12}>
<GridCanvas style={{ height: 480 }}>
{layout.items.map((item) => (
<GridItem key={item.id} id={item.id} resizeEdges={['e', 's', 'se']}>
{item.data.label}
</GridItem>
))}
</GridCanvas>
</GridProvider>
)
}Browse the examples · Open the sample studio · Read the docs
- Move with intent.
moveIteminfers what you meant from how the item overlaps its neighbors: slide a row aside, swap two cards, reorder a column, drop into an open pocket, or trim an oversized neighbor. Every result names the strategy it used. - Resize that makes room.
resizeItemsnaps the dragged edge to nearby edges and shrinks only the neighbors the new rectangle actually collides with. - Place and transfer.
placeIteminserts a new item at a position or centered on a pointer;transferItemmoves an item between two layouts and keeps its visual size when the canvases differ in scale. - Project onto any size.
projectLayouttreats rows and columns as flex chains: fixed-size items and configured gaps keep their pixels, everything else fills the remaining space proportionally. A second, simpler segment-based strategy is available. - Nest without nesting DOM.
flattenLayoutwalks a tree of layouts and returns every item in root coordinates, so containment is math, not stacking contexts. Bring your own tree shape through an adapter. - Policies, not flags. Items can be
locked(a wall that never moves) orignorecollisions (a ghost others move through). Sizing modes pin width, height, or both across projections. - Deterministic and immutable. Equal inputs give equal outputs. Inputs are never mutated. Property tests enforce bounds, non-overlap, id conservation, and constraint preservation on random layouts.
gridla |
gridla/react |
|
|---|---|---|
| Runtime dependencies | none | none (React ≥ 18 as a peer) |
| Touches the DOM | never | measurement and pointer events only |
| Owns state | never | transient gesture state; layout state stays yours |
| Works in | browsers, Node, workers, SSR | browsers, SSR-safe imports |
| Size (min + gzip) | about 23 kB | about 1 kB more than the core |
The React adapter puts a tiny store in context and reads it with
useSyncExternalStore, so a pointer move rerenders only the items whose
rectangles changed. Controlled and uncontrolled modes are both supported.
Cross-canvas moves are opt-in through GridTransferScope; nested layouts are
just providers inside items.
Every adapter is a subpath of the same package, built on the framework-neutral
gridla/interaction layer, with the framework as an optional peer dependency.
Same component names, same props, same data-gridla-* attributes everywhere.
| Framework | Import | Docs |
|---|---|---|
| React | gridla/react |
https://wintercounter.github.io/gridla/getting-started/react.html |
| Vue | gridla/vue |
https://wintercounter.github.io/gridla/adapters/vue.html |
| Svelte | gridla/svelte |
https://wintercounter.github.io/gridla/adapters/svelte.html |
| Solid | gridla/solid |
https://wintercounter.github.io/gridla/adapters/solid.html |
| Angular | gridla/angular |
https://wintercounter.github.io/gridla/adapters/angular.html |
| Qwik | gridla/qwik |
https://wintercounter.github.io/gridla/adapters/qwik.html |
| Web Components | gridla/elements |
https://wintercounter.github.io/gridla/adapters/web-components.html |
| DOM (vanilla) | gridla/dom |
https://wintercounter.github.io/gridla/adapters/dom.html |
| Preact | gridla/react |
https://wintercounter.github.io/gridla/adapters/preact.html |
Writing one for another framework: https://wintercounter.github.io/gridla/adapters/#write-your-own-adapter
Core operations: moveItem, resizeItem, placeItem, transferItem,
projectLayout, applyGap, flattenLayout, compactLayout, applyPreset.
Geometry and validation helpers: createItem, normalizeLayout, clampItem,
resizeRect, findLayoutViolations, hitTest, findContainerAt.
React: GridProvider, GridCanvas, GridItem, GridPreviewOutline,
GridTransferScope, useGridActions, useGridItemView, useGridLayout,
useGridStore, useGridInteraction.
Full reference: https://wintercounter.github.io/gridla/api/
- Browsers: current Chrome, Edge, Firefox, and Safari. Pointer Events and
ResizeObserverare required by the adapters; the core runs anywhere ES2022 does. - Node 20.19 or newer. ESM only.
- TypeScript declarations ship with the package. The public types are the documented contract; anything not exported from a package entry point is internal.
bun install
bun run check # format, lint, typecheck, unit + compatibility + invariant tests
bun run build # library
bun run bench # solver and projection benchmarks
bun run test:e2e # Playwright (Chromium, Firefox, WebKit) plus an Obscura CDP lane
bun run dev:gallery # examples app
bun run dev:studio # sample studioSee CONTRIBUTING.md for the workflow, SECURITY.md for reporting vulnerabilities, and CHANGELOG.md for releases. Releases are published from CI with npm provenance.
The solver and projection behavior were refined over a long period inside a production dashboard builder before being extracted here with a compatibility suite of more than four hundred fixtures. All visual assets are hand-authored SVG; fonts are Familjen Grotesk, Instrument Sans, and JetBrains Mono.
MIT © Viktor Vincze