<smart-grid>

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.

auto-fit Responsive 5 Breakpoints Drag to Reorder Resize Handles Persist Layout Masonry / Dense
<script src="smart-grid.js"></script>

Live Playground

Adjust every attribute in real time. Toggle features. Add and remove cards dynamically.

off
off
off
Live Preview
Revenue Analyticschart
Monthly recurring revenue
span 2
Active Userskpi
24.8k
↑ 12% vs last month
Conversionkpi
3.4%
↓ 0.2% vs last week
Recent Transactionstable
UserAmountStatusDate
ketan_dev$249paidtoday
ananya_m$99pendingyesterday
span 3
Region Mapmap
🗺 Geographic distribution
span 2
Avg Sessionkpi
4m 38s
↑ 8% engagement

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.

col 1
col 2
col 3
span 2
normal
<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.

xs · ≤ 480px sm · ≤ 640px md · ≤ 1024px lg · ≤ 1280px xl · ∞
<!-- 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.

span 4 — full width
span 2
1
1
span 3
1
<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.

row-span 2
normal
normal
span 2
<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=.

ElementDefault column spanDefault row span
<smart-chart>21
<smart-table>31
<smart-kpi>11
<smart-map>22
any other element11
<!-- 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.

Drag me · span 2
Drag me
Drag me
Drag me
↑ Drag any card to reorder
<!-- 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=.

row-span 2
normal
normal
span 2 · fills gap
<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

AttributeTypeDescriptionDefault
columnsstringauto-fit · auto-fill · or a number. auto-fit/fill requires min=. Omit for auto-detect from child count.auto
minstringMinimum column width for auto-fit/fill mode. Any CSS length: 280px, 20rem.280px
gapnumber|stringGrid gap between cells. Numbers are treated as px. Any CSS length works.16px
row-heightstringFixed height for each row track. Without this, rows size to content. Numbers treated as px.auto
xsnumberColumn count at container width ≤ 480px.
smnumberColumn count at container width ≤ 640px.
mdnumberColumn count at container width ≤ 1024px.
lgnumberColumn count at container width ≤ 1280px.
xlnumberColumn count at any width above lg.
draggablebooleanEnable HTML5 drag-to-reorder. All children get draggable="true" and cursor:grab.false
resizablebooleanAttach right and bottom resize handles to all children. Drag to change column/row span.false
persiststringLocalStorage key for layout persistence. Saves drag order and span sizes. Restores on page load. Cleared via grid.clearLayout().
masonrybooleanEnable grid-auto-flow: dense for gap-filling compact layouts.false

Child Attributes

AttributeTypeDescription
spannumberColumn span for this child. Clamped to the grid's current column count on resize.
row-spannumberRow span for this child. Works best with a fixed row-height on the grid.
id / data-sg-idstringOptional stable identifier for layout persistence. If omitted, item index is used.

Public API

MethodDescription
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();
});