SmartGrid
A declarative CSS Grid layout engine for building responsive dashboards.
Set columns, spans, gaps, row heights, breakpoints, drag-to-reorder, resize handles,
masonry flow, and localStorage persistence — all from HTML attributes.
Works with any content: SmartComponents, plain divs, Bootstrap cards, anything.
<script src="smart-grid.js"></script>
Live Playground
Adjust every attribute in real time. Toggle features. Add and remove cards dynamically.
auto-fit / auto-fill
The most responsive pattern — grid columns shrink to fit as many as possible based on
a min width, with no breakpoints needed. The difference:
auto-fit collapses empty tracks (stretches items to fill), while
auto-fill preserves empty column slots.
<!-- Most common — responsive, no breakpoints --> <smart-grid columns="auto-fit" min="280px" gap="20" > <div span="2">Wide card</div> <div>Normal</div> </smart-grid> // At 1200px wide, min=280px → 4 columns // At 768px wide, min=280px → 2 columns // At 360px wide, min=280px → 1 column // All automatic — zero media queries.
Fixed Columns
Use a plain number for a fixed column count. Useful when you want predictable layouts or are combining with breakpoint attributes.
<smart-grid columns="3" gap="16" > <div>col 1</div> <div>col 2</div> <div>col 3</div> <div span="2">span 2</div> <div>normal</div> </smart-grid>
Responsive Breakpoints
Add xs=, sm=, md=, lg=, or
xl= to override the column count at specific widths. The grid uses
offsetWidth — not window.innerWidth — so it responds to
container width changes too, not just viewport resizes.
<!-- 4 cols on desktop → 2 on tablet → 1 on mobile --> <smart-grid columns="4" xs="1" sm="1" md="2" lg="4" gap="16" > ... </smart-grid> // Breakpoint evaluation order: xs → sm → md → lg → xl → columns // First matching breakpoint wins (smallest to largest). // Resize the browser to see the columns change live.
Column Span
Add span="N" to any child to make it span N columns.
SmartGrid automatically clamps the span to the current column count — if you set
span="3" on a 2-column grid, it clamps to 2. Clamping is re-applied
reactively on every resize event.
<smart-grid columns="4" gap="16"> <div span="4">Full-width header</div> <div span="2">Wide chart</div> <div>KPI 1</div> <div>KPI 2</div> <div span="3">Table</div> <div>KPI 3</div> </smart-grid> // span is auto-clamped to column count on resize.
Row Span
Use row-span="N" on a child to make it span N row tracks. Works best with
row-height= set to a fixed value so tall cards take a predictable amount of space.
Combine with masonry to fill gaps automatically.
<smart-grid columns="3" row-height="160px" gap="16" > <div row-span="2">Tall map card</div> <div>Normal</div> <div>Normal</div> <div span="2">Wide below</div> </smart-grid>
Smart Type Defaults
When placing known SmartComponents directly inside <smart-grid>,
sensible default spans are applied automatically — no span= attribute needed.
You can always override with an explicit span=.
| Element | Default column span | Default row span |
|---|---|---|
| <smart-chart> | 2 | 1 |
| <smart-table> | 3 | 1 |
| <smart-kpi> | 1 | 1 |
| <smart-map> | 2 | 2 |
| any other element | 1 | 1 |
<!-- smart-chart auto-spans 2 cols, smart-table auto-spans 3 --> <smart-grid columns="auto-fit" min="280px"> <smart-chart api="/api/revenue/" ...></smart-chart> // span 2 <smart-kpi></smart-kpi> // span 1 <smart-table api-url="/api/users/" ...></smart-table> // span 3 </smart-grid> <!-- Override with explicit span= --> <smart-chart span="3" ...></smart-chart>
Drag to Reorder
Add the draggable boolean attribute. All children become draggable (cursor:grab).
Drop a card to the left or right of any other card — SmartGrid uses the midpoint of the
target card to decide the insert position. A dashed green outline shows the drop target.
Uses the native HTML5 Drag & Drop API — no external libraries.
<!-- Add draggable — that's it --> <smart-grid columns="3" draggable gap="16" > <div>Card A</div> <div>Card B</div> <div>Card C</div> </smart-grid> // Drop position determined by x-midpoint of target card. // .sg-drag-over class applied to active drop target for styling.
Resize Handles
Add the resizable attribute. A 6px-wide transparent handle appears on the
right and bottom edges of each card. Hover to see the green highlight. Drag right to
expand column span, drag down to expand row span. Snap is based on column/row track size.
Combine with persist= to save resized layouts.
<smart-grid columns="3" draggable resizable gap="16" > <div>Drag right edge to widen</div> <div>Drag bottom edge to heighten</div> </smart-grid> // Resize handles: right edge → column span, bottom edge → row span // Span is snapped to nearest whole column/row track. // Handles turn rgba(74,222,128,.4) green on hover.
Persist Layout
Add persist="myKey". Every drag-to-reorder and resize operation is saved
to localStorage under the key sg:myKey. On next page load,
the layout is restored from storage before the first render. Call
grid.clearLayout() to wipe saved state and return to defaults.
<!-- Drag + resize + persist — full user-customizable dashboard --> <smart-grid columns="3" draggable resizable persist="myDashboard" gap="16" > <div id="revenue-chart" span="2">...</div> <div id="users-kpi">...</div> </smart-grid> // Saved to: localStorage["sg:myDashboard"] // Shape: [{ index, id, gridColumn, gridRow }] // Cleared with: document.querySelector('smart-grid').clearLayout(); // Note: items are matched by index. For stable restore across dynamic content, // add a data-sg-id or id attribute to each child.
Masonry / Dense Fill
Add the masonry attribute to enable grid-auto-flow: dense.
CSS Grid will attempt to fill empty gaps left by large-span items with subsequent
smaller items — creating a compact masonry-style layout. Great for variable-height cards
combined with row-height=.
<smart-grid columns="3" masonry row-height="160px" gap="12" > <div row-span="2">Tall card</div> <div>Normal</div> <div>Normal</div> <div span="2">Fills gap automatically</div> </smart-grid> // grid-auto-flow: dense makes subsequent items // fill gaps left by wide/tall earlier items.
Dynamic Items
SmartGrid watches for DOM mutations via MutationObserver. Adding or removing
children triggers a full recalculation — columns are recomputed and spans re-applied.
Use addItem(el, opts) for the cleanest programmatic API.
const grid = document.querySelector('smart-grid'); // Method 1: Public addItem API grid.addItem(myElement, { span: 2, rowSpan: 1 }); // Method 2: Direct appendChild — MutationObserver handles the rest const card = document.createElement('div'); card.setAttribute('span', '2'); card.innerHTML = '<p>New card</p>'; grid.appendChild(card); // SmartGrid auto-recalculates // Refresh manually after bulk changes grid.refresh();
Attributes
| Attribute | Type | Description | Default |
|---|---|---|---|
| columns | string | auto-fit · auto-fill · or a number. auto-fit/fill requires min=. Omit for auto-detect from child count. | auto |
| min | string | Minimum column width for auto-fit/fill mode. Any CSS length: 280px, 20rem. | 280px |
| gap | number|string | Grid gap between cells. Numbers are treated as px. Any CSS length works. | 16px |
| row-height | string | Fixed height for each row track. Without this, rows size to content. Numbers treated as px. | auto |
| xs | number | Column count at container width ≤ 480px. | — |
| sm | number | Column count at container width ≤ 640px. | — |
| md | number | Column count at container width ≤ 1024px. | — |
| lg | number | Column count at container width ≤ 1280px. | — |
| xl | number | Column count at any width above lg. | — |
| draggable | boolean | Enable HTML5 drag-to-reorder. All children get draggable="true" and cursor:grab. | false |
| resizable | boolean | Attach right and bottom resize handles to all children. Drag to change column/row span. | false |
| persist | string | LocalStorage key for layout persistence. Saves drag order and span sizes. Restores on page load. Cleared via grid.clearLayout(). | — |
| masonry | boolean | Enable grid-auto-flow: dense for gap-filling compact layouts. | false |
Child Attributes
| Attribute | Type | Description |
|---|---|---|
| span | number | Column span for this child. Clamped to the grid's current column count on resize. |
| row-span | number | Row span for this child. Works best with a fixed row-height on the grid. |
| id / data-sg-id | string | Optional stable identifier for layout persistence. If omitted, item index is used. |
Public API
| Method | Description |
|---|---|
| refresh() | Recalculate column count and re-apply all child spans. Call after programmatic attribute changes. |
| addItem(el, opts) | Append an element to the grid with optional opts.span and opts.rowSpan. MutationObserver handles layout update automatically. |
| clearLayout() | Remove the persisted layout from localStorage and revert all children to their default spans. |
const grid = document.querySelector('smart-grid'); // Programmatically add a new card const el = document.createElement('div'); el.textContent = 'New Widget'; grid.addItem(el, { span: 2 }); // Force re-layout after bulk changes grid.refresh(); // Reset saved user layout back to defaults document.getElementById('reset-btn').addEventListener('click', () => { grid.clearLayout(); });