EG Grid

Zero-dependency CSS Grid drag-and-drop. One file.

28 KB min · 8.4 KB gzip · 0 deps
A (2×1)
B
C
D (1×2)
E (2×1)
F
G
H (2×1)

Drag to rearrange. Resize from corners and edges. Arrow keys to navigate.

Architecture Explorer Web Component Frameworks Datastar + Workers

Comparison

EG Grid Packery gridster.js react-grid-layout
Minified28 KB~38 KB~32 KB~43 KB
Gzipped8.4 KB~12 KB~10 KB~13 KB
Dependencies00 (bundled)jQuery (~30 KB)React (~42 KB)
Total w/ deps28 KB~48 KB~62 KB~85 KB+
FrameworknonenonejQueryReact only
Layout engineCSS Gridabs positioningabs positioningCSS transforms
Dragyesyes (addon)yesyes
Resize (8-way)yesbyoSE onlySE only
Keyboardyesbyobyobyo
Accessibilityyes (ARIA)byobyobyo
View Transitionsyesbyobyobyo
Vendorable1 filenpm/CDNnpm/CDNnpm only

Performance

Frame times measured on a live 8-item grid. All interactions stay under 16ms (60fps budget).

16ms 60fps budget 12ms 8ms 4ms 0ms Drag (30 frames) 8.1ms avg 9.2ms p95 Keyboard (8 moves) 7.3ms avg 8.9ms p95 Resize (20 frames) 8.1ms avg 9.2ms p95

Measured via Chrome DevTools on the grid above. requestAnimationFrame timing, 8 items, push algorithm.

Quick Start

Download a bundle

// Core library (8.4 KB gzip)
curl -fsSL https://raw.githubusercontent.com/derekr/eg-grid/main/dist/eg-grid.js -o lib/eg-grid.js

// Web component (10.5 KB gzip)
curl -fsSL https://raw.githubusercontent.com/derekr/eg-grid/main/dist/eg-grid-element.js -o lib/eg-grid-element.js

Interactive setup

Or run the vendor script — picks a bundle, configures your grid, outputs starter HTML and CSS:

curl -fsSL https://raw.githubusercontent.com/derekr/eg-grid/main/vendor.sh | bash

Core library

<style id="egg-styles">
  [data-egg-item="a"] { grid-column: 1 / span 2; grid-row: 1; }
  [data-egg-item="b"] { grid-column: 3; grid-row: 1; }
  [data-egg-item="c"] { grid-column: 1; grid-row: 2; }
</style>

<div class="grid" id="grid">
  <div data-egg-item="a">A</div>
  <div data-egg-item="b">B</div>
  <div data-egg-item="c">C</div>
</div>

<script type="module">
  import { init } from './lib/eg-grid.js';

  const core = init(document.getElementById('grid'), {
    algorithm: 'push',
    styleElement: document.getElementById('egg-styles'),
    resize: { handles: 'all' },
  });
</script>

Web Component

<script type="module" src="lib/eg-grid-element.js"></script>

<style>
  [data-egg-item="a"] { grid-column: 1 / span 2; grid-row: 1; }
  [data-egg-item="b"] { grid-column: 3; grid-row: 1; }
  [data-egg-item="c"] { grid-column: 1; grid-row: 2; }
</style>

<eg-grid columns="4" gap="8" algorithm="push" resize-handles="all">
  <div data-egg-item="a">A</div>
  <div data-egg-item="b">B</div>
  <div data-egg-item="c">C</div>
</eg-grid>

How It Works

CSS Grid does the layout. JavaScript only sets grid-column and grid-row via <style> injection. The browser computes all pixel positions, handles animations via View Transitions, and manages responsive behavior through container queries.

During drag, the item leaves grid flow (position: fixed) so the grid can reflow around it. On drop, it rejoins the grid at its new position.

State machine: idleselectedinteractingidle. Three phases, tracked on core.phase.

Features

Everything is in one file. No plugins to configure — just init() options.

Drag & dropPointer and touch. Push algorithm.
Resize (8-way)Corners and edges. Min/max size constraints.
KeyboardArrow keys, hjkl, Enter/Space grab, Shift+G mode.
AccessibilityARIA live region announces all interactions.
View TransitionsAnimated layout changes when the browser supports it.
ResponsiveContainer query breakpoints. Auto-reflow on resize.
Camera scrollAuto-scroll viewport when dragging near edges.
PlaceholderVisual drop target indicator. Styled via CSS class.
OptionDefaultDescription
algorithmfalse'push' or false
resizeenabled{ handles: 'corners' | 'edges' | 'all' } or false
cameraenabledAuto-scroll during drag. false to disable
placeholderenabledDrop target indicator. { className } or false
keyboardenabledArrow key navigation. false to disable
accessibilityenabledARIA announcements. false to disable
pointerenabledMouse/touch drag. false to disable
responsivedisabled{ layoutModel, cellSize, gap }
styleElementauto-created<style> element for CSS injection

API

const core = init(element, options);

core.phase          // 'idle' | 'selected' | 'interacting'
core.interaction    // { type, mode, itemId, element, columnCount } | null
core.selectedItem   // HTMLElement | null
core.select(item)   // Programmatic select
core.deselect()     // Programmatic deselect
core.baseCSS        // Get/set base layout CSS
core.previewCSS     // Get/set preview CSS (during drag/resize)
core.commitStyles() // Flush CSS to <style> element
core.destroy()      // Clean up all listeners

Events

All events bubble from the grid element. source is 'pointer' or 'keyboard'.

EventDetail
egg-drag-start{ item, cell, colspan, rowspan, source }
egg-drag-move{ item, cell, colspan, rowspan, source }
egg-drag-end{ item, cell, colspan, rowspan, source }
egg-drag-cancel{ item, source }
egg-resize-start{ item, handle, colspan, rowspan, source }
egg-resize-move{ item, handle, colspan, rowspan, source }
egg-resize-end{ item, handle, colspan, rowspan, source }
egg-resize-cancel{ item, source }
egg-select{ item }
egg-deselect{ item }
egg-column-count-change{ columnCount }

Keyboard

KeyAction
Arrow keys / hjklNudge item by 1 cell
Shift+ArrowResize item
Ctrl+ArrowJump by item size
Alt+ArrowSelect adjacent item
Enter / SpacePick up / drop item
EscapeCancel or deselect
Shift+GToggle keyboard mode

Styling

Items are styled via CSS attribute selectors. The library sets data attributes automatically:

[data-egg-dragging]  { cursor: grabbing; z-index: 100; }
[data-egg-dropping]  { z-index: 100; }
[data-egg-selected]  { outline: 2px solid #fbbf24; z-index: 10; }
[data-egg-resizing]  { z-index: 100; }
[data-egg-handle-hover="se"]::after { /* show SE handle */ }
[data-egg-keyboard-mode] { outline: 2px solid gold; }

View Transitions

For animated layout changes, give items a view-transition-name:

.item { view-transition-name: var(--item-id); }

::view-transition-group(*) {
  animation-duration: 200ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

Item Attributes

AttributePurpose
data-egg-itemMark as grid item (value = item ID)
data-egg-colspanColumn span (default: 1, or derived from CSS)
data-egg-rowspanRow span (default: 1, or derived from CSS)
data-egg-labelHuman-readable name for accessibility

Vendor-First

This is not a package you install. Copy one file into your codebase and own it.

Source

src/
  eg-grid.ts            ← THE library (1,165 lines, everything in one file)
  eg-grid-element.ts    ← <eg-grid> web component wrapper
  layout-model.ts       ← Responsive layout model (optional)
  bundles/element.ts    ← Web component bundle entry
  plugins/dev-overlay.ts← Debug panel (Shift+D, optional)
BundleGzipDescription
eg-grid.js8.4 KBCore library
eg-grid-element.js10.5 KBWeb component
dev-overlay.js3.1 KBDebug panel (Shift+D)