GridKit

Documentation

Everything you need to add drag, resize, and reorder to your app.

Installation

npm install gridkit-layout

Or with yarn/pnpm:

yarn add gridkit-layout
pnpm add gridkit-layout

Quick Start

Create a grid, add items, and you're done.

import { GridKit } from 'gridkit-layout'

// Create a grid inside a container element
const grid = GridKit.create('#dashboard', {
  collision: true,
  animate: true,
  bounds: true
})

// Add items
grid.add({
  id: 'widget-1',
  x: 0,
  y: 0,
  w: 400,
  h: 200,
  content: document.getElementById('my-widget')
})

// Listen for changes
grid.on('change', (items) => {
  localStorage.setItem('layout', JSON.stringify(items))
})

API Reference

GridKit.create(el, options?)

Creates a new GridKit instance. Accepts a selector string or HTMLElement.

const grid = GridKit.create('#container', { collision: true })

grid.add(item)

Adds an item to the grid. Returns the created grid item.

grid.add({
  id: 'unique-id',
  x: 0, y: 0,        // position in pixels
  w: 300, h: 200,    // size in pixels
  content: element,   // HTMLElement to render
  minW: 100,         // optional min width
  minH: 80,          // optional min height
  maxW: 600,         // optional max width
  maxH: 400,         // optional max height
  draggable: true,   // default: true
  resizable: true    // default: true
})

grid.remove(id)

Removes an item by ID.

grid.remove('widget-1')

grid.update(id, props)

Updates item properties (position, size, constraints).

grid.update('widget-1', { x: 100, y: 50, w: 500 })

grid.getItems()

Returns all items with their current positions and sizes.

const items = grid.getItems()
// [{ id, x, y, w, h, ... }]

grid.layout()

Forces a layout recalculation. Useful after container resize.

window.addEventListener('resize', () => grid.layout())

grid.destroy()

Removes all event listeners and cleans up. Call on unmount.

grid.destroy()

Options

Pass to GridKit.create() as the second argument. All features are opt-in.

OptionTypeDefaultDescription
collisionbooleanfalseEnable collision detection and auto-push
animatebooleanfalseAnimate item transitions
boundsbooleanfalseConstrain items within container
gapnumber0Gap between items in pixels
paddingnumber0Container inner padding
resizeHandles'all' | Direction[]'all'Which handles to show
onChange(items) => voidundefinedCalled when any item changes position/size

Events

Subscribe via options callbacks.

onDragStart(item, event)Fired when dragging begins
onDrag(item, event)Fired continuously during drag
onDragEnd(item, event)Fired when dragging ends
onResizeStart(item, edge, event)Fired when resizing begins
onResize(item, edge, event)Fired continuously during resize
onResizeEnd(item, edge, event)Fired when resizing ends
onChange(items)Fired after any layout change

Styling

GridKit applies minimal styles. Target items with data attributes.

/* Target any grid item */
[data-gridkit-id] {
  border-radius: 8px;
  background: #1a1a2e;
  border: 1px solid #292d30;
}

/* Style resize handles */
[data-gridkit-handle] {
  opacity: 0;
  transition: opacity 0.2s;
}
[data-gridkit-id]:hover [data-gridkit-handle] {
  opacity: 1;
}

/* Style drag zone */
[data-gridkit-drag] {
  cursor: grab;
}

Framework Guides

React

import { useRef, useEffect } from 'react'
import { GridKit } from 'gridkit-layout'

function Dashboard() {
  const containerRef = useRef(null)

  useEffect(() => {
    const grid = GridKit.create(containerRef.current, {
      collision: true,
      animate: true
    })
    grid.add({ id: '1', x: 0, y: 0, w: 300, h: 200 })
    return () => grid.destroy()
  }, [])

  return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
}

Angular

import { Component, ViewChild, ElementRef, AfterViewInit, OnDestroy } from '@angular/core'
import { GridKit } from 'gridkit-layout'

@Component({
  selector: 'app-dashboard',
  template: '<div #container style="width:100%;height:600px"></div>'
})
export class DashboardComponent implements AfterViewInit, OnDestroy {
  @ViewChild('container') container!: ElementRef
  private grid!: ReturnType<typeof GridKit.create>

  ngAfterViewInit() {
    this.grid = GridKit.create(this.container.nativeElement, { collision: true, animate: true })
    this.grid.add({ id: '1', x: 0, y: 0, w: 300, h: 200 })
  }

  ngOnDestroy() { this.grid.destroy() }
}

Vue

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { GridKit } from 'gridkit-layout'

const container = ref(null)
let grid

onMounted(() => {
  grid = GridKit.create(container.value, { collision: true, animate: true })
  grid.add({ id: '1', x: 0, y: 0, w: 300, h: 200 })
})

onUnmounted(() => grid?.destroy())
</script>

<template>
  <div ref="container" style="width:100%;height:600px" />
</template>

Examples

Save & Restore Layout

const grid = GridKit.create('#app', {
  onChange: (items) => {
    localStorage.setItem('layout', JSON.stringify(items))
  }
})

// Restore on load
const saved = JSON.parse(localStorage.getItem('layout') || '[]')
saved.forEach(item => grid.add(item))

Dynamic Add/Remove

document.getElementById('add-btn').onclick = () => {
  grid.add({
    id: crypto.randomUUID(),
    x: 0, y: 0,
    w: 200, h: 150,
    content: createWidgetElement()
  })
}

document.getElementById('remove-btn').onclick = () => {
  const items = grid.getItems()
  if (items.length) grid.remove(items[items.length - 1].id)
}