A react-grid-layout alternative for dashboards that outgrow the grid — and React
Honest framing first: react-grid-layout is the standard React dashboard grid — draggable, resizable, responsive breakpoints, MIT, millions of weekly installs — and if you are all-React and a layout engine is all you need, use it. Grafloria Dashboards is for the moment the grid becomes an application: undo, nesting, persistence of the whole board, a splitter layout, and the same board in Angular and Vue.
The comparison here is with React grid layout engines. Where a BI product such as DevExpress Dashboard comes up below, it is only for how its designer aligns widgets — a splitter surface that is always covered — which the kit's split layout reproduces. Data sources, query builders and the rest of a BI suite are out of scope on both sides of this table.
When to stay with react-grid-layout
You are all-React and need exactly a grid: place, drag, resize, compact, responsive breakpoints with a layout per breakpoint. react-grid-layout does all of it, small and focused, and its item array is plain state you already own.
Feature by feature
✓ built in · DIY possible, but yours to build · — out of scope. Both columns are meant to be true, including the rows we lose.
| react-grid-layout | Grafloria Dashboards | |
|---|---|---|
| Grid & layout | ||
| Drag / resize / re-pack | ✓ — compactType vertical / horizontal / none | ✓ push-down with skip, swap shapes, truthful placeholder |
| Nested grids | — | ✓ containers — cross-boundary drag with adoption, one undo step |
| Responsive columns | ✓ ResponsiveGridLayout, a layout per breakpoint | ✓ cells re-flow, not just clamp |
| Locked tiles | ✓ static, isDraggable/isResizable per item | ✓ pinned, movable, resizable — pinned survives every reflow |
| Fluid width — 100% of the container | ✓ WidthProvider | ✓ the default; mode: 'fixed' keeps an authored world for a board inside a diagram |
| Per-widget min / max size | ✓ minW … maxH | ✓ limits: { minSpan, maxSpan, minRows, maxRows } |
| Resize from any edge | ✓ resizeHandles | ✓ every edge and corner, the cursor says which |
| Static (read-only) grid | ✓ isDraggable={false} + isResizable={false} | ✓ static: true, switched live with setStatic() |
| Drag handle | ✓ draggableHandle / draggableCancel | ✓ dragHandle — the caption strip, your own selector, or a painted grip (left / center / right, in the header or a tab above the card) shown on the selected widget only; switched live |
| Sizing modes | ✓ rowHeight, maxRows, autoSize | ✓ sizing: 'grow' — rows keep their height, the board extends; 'fit' — rows squeeze, the board never scrolls; switched live |
| Splitter layout — the surface is always covered | — | ✓ layout: 'split' — the way a BI dashboard designer (DevExpress's, for one) aligns widgets: one fills, the next halves, dividers are percentages, a drop lands on an edge; switched live, same document |
| Bounded height | ✓ maxRows | ✓ fit mode is a capacity — a board never paints past its own edge |
| Keyboard operation | not documented | ✓ arrows move, Shift+arrows resize, every outcome announced — axe-core in CI |
| Touch | ✓ | ✓ drag and resize, 24-px handles visible without hover |
| Widgets & content | ||
| Content model | children keyed by i — rendering is yours | kind + data — widgets are data |
| Built-in renderers (KPI, line, donut…) | — | ✓ six, themed |
| Your own charts | DIY as children | ✓ renderWidget seam — mount anything |
| Editing | ||
| Undo / redo | — | ✓ every gesture one command, ⌘Z included |
| Cross-container gestures | — | ✓ adoption + escalation, one undo |
| Persistence & sharing | ||
| Save / load | ✓ onLayoutChange gives you the layout array | ✓ the whole board — widgets, nesting, data |
| Export PNG / SVG / PDF | — | ✓ client and server-side |
| Diagrams in the same document | — | ✓ one engine, one format, one undo stack |
| Real-time collaboration | — | ✓ per-property CRDT, transport-agnostic |
| Project | ||
| License | MIT | MIT — no pro tier |
| Dependencies | react-draggable, react-resizable | a handful; ELK layout is a lazy chunk (sizes) |
| Framework wrappers | React only | ✓ native components, same three + web component |
| Maturity | ✓ a decade in production, one of the most installed React libraries | newer — every claim above is a CI-tested demo you can open |
The same dashboard, both ways
react-grid-layout lays out children you render; the layout array is your state:
import GridLayout from 'react-grid-layout';
const layout = [
{ i: 'rev', x: 0, y: 0, w: 3, h: 1 },
{ i: 'trend', x: 3, y: 0, w: 8, h: 2 },
];
<GridLayout layout={layout} cols={12} rowHeight={30} width={1200} onLayoutChange={save}>
<div key="rev" className="kpi">Total revenue</div>
<div key="trend"><canvas id="trend" /></div> {/* chart wiring is yours */}
</GridLayout>
Grafloria declares the widgets too — the chart is the declaration — and in React it is one component:
import { GrafloriaDashboard } from '@grafloria/react';
import { dashboard } from '@grafloria/dashboard';
// <GrafloriaDashboard views={views} layout="grid" sizing="fit" onReady={h => (handle = h)} />
// …or without React, the same board:
const SPEC = dashboard({
columns: 12,
widgets: [
{ id: 'rev', kind: 'kpi', span: 3, rows: 1,
data: { label: 'Total revenue', value: '$6.81M', delta: 12.4 } },
{ id: 'trend', kind: 'line', span: 8, rows: 2, title: 'Revenue vs target',
data: { series: [{ name: 'Revenue', values: [420,455,512,588,648] }],
labels: ['Jan','Feb','Mar','Apr','May'] } },
],
});
render(SPEC, document.getElementById('canvas'));
// SPEC.handle.toJSON() round-trips the WHOLE board — widgets, nesting, data
How the concepts map
| react-grid-layout | Grafloria Dashboards |
|---|---|
x, y, w, h on a layout item | x, y, span, rows — or omit them and widgets flow in declaration order |
children keyed by i | kind + data — six built-in renderers (KPI, line, donut…), or your own via renderWidget |
onLayoutChange(layout) / the layout prop | toJSON() ↔ dashboard() — one document format shared with diagrams |
| no equivalent | a widget carrying widgets — cross-boundary drag with adoption, one undo step |
static: true on an item | pinned: true — never pushed, survives every reflow |
cols per breakpoint, layouts per breakpoint | columns + responsive re-flow of the cells themselves |
isDraggable={false} isResizable={false} | static: true — the viewer's mode; the API and undo keep working |
draggableHandle selector | dragHandle: true | '.sel' | { grip: true, position, placement } — caption strip, your element, or a grip on the selected widget |
| no equivalent | layout: 'split' — a splitter tree beside the grid, setLayout() keeps the picture either way |
minW / maxW / minH / maxH | limits: { minSpan, maxSpan, minRows, maxRows } on the widget |
maxRows | sizing: 'fit' — the board's height is the capacity; overflow: 'scroll' opts out |
See the difference in one demo
The clearest way to judge the boundary: react-grid-layout gives you the grid geometry;
this demo is a dashboard whose
sections are containers — drag a tile across a section boundary and the other grid adopts it
live, one undo restores the whole gesture, and toJSON() serializes the nesting.
The grid options demo shows column
counts, packing and responsive re-flow driven as data, and the
fluid dashboard has Fit / Grow, Grid / Split
and the drag-handle modes in one toolbar.
Pricing and feature cells reflect vendor pages and shipped type definitions as of July–September 2026 — always confirm on the vendor's site. Corrections welcome on GitHub; this page aims to stay honest, not to win.