mrkeyoor.com_
Sat 08 Aug 21:57 UTC
npmWeb Frontendupdated 08 Aug 2026

react-grid-layout

react-grid-layout is a React dashboard layout engine for draggable and resizable rectangular widgets. You describe each widget in grid units with an id, column and row position, width, height, and optional constraints; the component converts that model to pixels and resolves collisions. It supports fixed and responsive grids, static items, horizontal or vertical compaction, free positioning, external drops, scaled containers, serialization, and lower-level hooks. Version 2 is a TypeScript rewrite with a new grouped configuration API and a legacy entry point for version 1 applications.

Verdict

The strongest general-purpose choice for a React dashboard whose layout is genuinely user-editable, and version 2 finally brings first-party types and modular internals. Do not install it for a normal responsive page, and budget real work for persistence, migration, SSR sizing, and keyboard controls.

API stability3/5The core layout model and version 1 behavior lasted for years, and version 2 provides a dedicated legacy export for runtime compatibility. Still, the current major is a complete TypeScript rewrite with documented breaking changes: width is required, data-grid moved to legacy, flat props became grouped configs, callback parameters became immutable, verticalCompact and UMD output disappeared, and drag-start timing changed. The migration path is thoughtful, but this is not a drop-in major upgrade.
Docs5/5The README covers installation CSS, a version 1 migration table, fixed and responsive examples, four width strategies, all three hooks, detailed TypeScript interfaces, compactor and positioning extension points, performance guidance, and more than twenty live demos. It states defaults and explains subtle changes such as the three-pixel drag threshold and immutable callbacks. Some examples mix component names and legacy concepts, so the migration section still deserves close reading.
Maintenance5/5Version 2.2.4 was published on July 29, 2026, and GitHub reports a push on August 7, 2026, one day before this guide's update. The project recently completed a TypeScript rewrite, publishes typed ESM and CommonJS subpath exports, maintains a legacy adapter, and documents its architecture. The repository has 62 open issues and PRs, a manageable queue for a 22,378-star interaction library with a broad browser surface.
Ecosystem5/5The package recorded 3,460,944 downloads from July 31 through August 6, 2026 and has 22,378 GitHub stars. Its README names production use in Grafana, Metabase, Kibana, HubSpot, Monday, and other dashboard products. It integrates with ordinary React children, exports framework-independent core algorithms, supplies first-party types, and keeps a legacy import for the large version 1 install base.

Use it if

  • You are building an editable dashboard whose widgets need both drag-to-reorder and resize handles
  • You need separate saved layouts for desktop, tablet, and phone breakpoints
  • Your layout must combine movable widgets with fixed items and minimum or maximum size constraints
  • You need React components plus reusable collision, compaction, and positioning algorithms from one package
Skip it if

Setup reality

Install react-grid-layout with React and ReactDOM, then import both react-grid-layout/css/styles.css and react-resizable/css/styles.css. Missing the second stylesheet is the classic first-run surprise: resize handles exist but look absent or wrong. Version 2.2.4 includes TypeScript types and ESM and CommonJS exports, and its README compatibility table targets React 18 and newer even though the package metadata still declares peers from React 16.3. The v2 component requires a numeric width. The recommended useContainerWidth hook gives you width, containerRef, and mounted; attach the ref to the wrapper and avoid rendering the grid until mounted, or provide a trusted initial width. For SSR, use measureBeforeMount and an initialWidth to limit hydration movement. Layout ids must match the React child keys, positions and sizes are grid units rather than pixels, and every breakpoint needs a compatible column count. The component reports layout changes but does not store them, so persist immutable copies and validate old saved layouts when widgets or constraints change. Importing from react-grid-layout uses the grouped v2 props such as gridConfig, dragConfig, and resizeConfig. Existing v1 code should temporarily import from react-grid-layout/legacy; data-grid on children, WidthProvider, and old flat props live there. The migration is not cosmetic: width became required, drag-start waits for three pixels of movement, callback parameters are read-only, verticalCompact was removed, and the UMD build disappeared. Transformed parent containers also need createScaledStrategy with the actual scale or pointer coordinates drift. Finally, dragging is not a complete accessibility design. Add explicit keyboard move and resize actions, announce changes, and persist only after a completed interaction rather than writing on every pointer movement.

Patterns

Create a measured version 2 gridcreate-basic-grid

import ReactGridLayout, { useContainerWidth } from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';

const layout = [
  { i: 'chart', x: 0, y: 0, w: 6, h: 3 },
  { i: 'totals', x: 6, y: 0, w: 3, h: 2 },
];

export function Dashboard() {
  const { width, containerRef, mounted } = useContainerWidth();
  return <div ref={containerRef}>
    {mounted && <ReactGridLayout width={width} layout={layout} gridConfig={{ cols: 12, rowHeight: 40 }}>
      <section key="chart">Chart</section>
      <section key="totals">Totals</section>
    </ReactGridLayout>}
  </div>;
}

Import both stylesheets. Every layout i must equal its child's React key, and version 2 requires an explicit measured width.

Provide layouts for multiple breakpointsbuild-responsive-grid

import { Responsive, useContainerWidth } from 'react-grid-layout';

const layouts = {
  lg: [{ i: 'sales', x: 0, y: 0, w: 6, h: 3 }],
  sm: [{ i: 'sales', x: 0, y: 0, w: 6, h: 3 }],
};

function ResponsiveDashboard() {
  const { width, containerRef, mounted } = useContainerWidth();
  return <div ref={containerRef}>
    {mounted && <Responsive width={width} layouts={layouts}
      breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
      cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}>
      <div key="sales">Sales</div>
    </Responsive>}
  </div>;
}

Store all breakpoint layouts, not only the active one. An item width must fit the column count at each breakpoint where it appears.

Save immutable layout changespersist-layout

function Dashboard() {
  const [layout, setLayout] = useState(loadLayout);

  const handleLayoutChange = (nextLayout) => {
    const saved = nextLayout.map((item) => ({ ...item }));
    setLayout(saved);
    localStorage.setItem('dashboard-layout-v2', JSON.stringify(saved));
  };

  return <ReactGridLayout width={width} layout={layout} onLayoutChange={handleLayoutChange}>...</ReactGridLayout>;
}

Version 2 callback parameters are read-only. Copy them instead of mutating, and version stored data so future widget and constraint changes can be migrated.

Pin one widget in placemake-static-widget

const layout = [
  { i: 'notice', x: 0, y: 0, w: 12, h: 1, static: true },
  { i: 'chart', x: 0, y: 1, w: 6, h: 4 },
];

A static item cannot be dragged or resized and also participates in collision handling, so movable items route around it.

Set minimum and maximum widget dimensionsconstrain-widget-size

const layout = [
  {
    i: 'chart', x: 0, y: 0, w: 6, h: 4,
    minW: 4, maxW: 10, minH: 3, maxH: 8,
  },
];

Constraints use grid units, not pixels. Keep minW at or below the smallest breakpoint's column count.

Limit dragging to a card handleconfigure-drag-handle

<ReactGridLayout
  width={width}
  layout={layout}
  dragConfig={{ enabled: true, handle: '.widget-handle', cancel: 'button,input,a' }}
>
  <article key="orders">
    <header className="widget-handle">Orders</header>
    <button>Refresh</button>
  </article>
</ReactGridLayout>

Use a cancel selector for interactive controls inside draggable cards. Version 2 starts a drag only after the pointer moves three pixels by default.

Expose more resize directionsconfigure-resize-handles

<ReactGridLayout
  width={width}
  layout={layout}
  resizeConfig={{
    enabled: true,
    handles: ['s', 'e', 'se', 'w'],
  }}
>
  {children}
</ReactGridLayout>

The default is only the southeast handle. The react-resizable stylesheet is required for visible, correctly positioned handles.

Preserve empty space in a free-positioned griddisable-compaction

import ReactGridLayout from 'react-grid-layout';
import { noCompactor } from 'react-grid-layout/core';

<ReactGridLayout width={width} layout={layout} compactor={noCompactor}>
  {children}
</ReactGridLayout>

Without compaction, gaps remain where users leave them. Collision behavior is separate, so choose prevent-collision or overlap semantics deliberately.

Pack widgets toward the leftcompact-horizontally

import { horizontalCompactor } from 'react-grid-layout';

<ReactGridLayout
  width={width}
  layout={layout}
  compactor={horizontalCompactor}
>
  {children}
</ReactGridLayout>

The default compactor is vertical. Horizontal compaction changes how collision resolution moves neighboring items, so test saved layouts before switching.

Correct pointer math inside a scaled containersupport-scaled-container

import ReactGridLayout from 'react-grid-layout';
import { createScaledStrategy } from 'react-grid-layout/core';

<div style={{ transform: 'scale(0.75)', transformOrigin: 'top left' }}>
  <ReactGridLayout
    width={width}
    layout={layout}
    positionStrategy={createScaledStrategy(0.75)}
  >
    {children}
  </ReactGridLayout>
</div>

The scale passed to createScaledStrategy must match the ancestor transform or dragging and resizing will drift away from the pointer.

Measure before showing an SSR gridprepare-for-ssr

function Dashboard() {
  const { width, containerRef, mounted } = useContainerWidth({
    measureBeforeMount: true,
    initialWidth: 1200,
  });

  return <div ref={containerRef}>
    {mounted ? <ReactGridLayout width={width} layout={layout}>{children}</ReactGridLayout> : null}
  </div>;
}

Choose an initialWidth close to the server-rendered container. Delaying until mounted avoids wrong geometry but can leave a visible blank area before hydration.

Keep the version 1 API during migrationmigrate-version-one

import GridLayout, { Responsive, WidthProvider } from 'react-grid-layout/legacy';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';

const AutoWidthGrid = WidthProvider(GridLayout);

<AutoWidthGrid cols={12} rowHeight={30}>
  <div key="a" data-grid={{ x: 0, y: 0, w: 4, h: 2 }}>A</div>
</AutoWidthGrid>

The legacy subpath provides version 1 runtime compatibility. New version 2 code should use an explicit layout, useContainerWidth, and grouped configuration props.

Alternatives

PackageRegistryPick it when
gridstacknpmChoose it for a framework-independent dashboard engine with official wrappers and no runtime dependencies
react-mosaic-componentnpmChoose it for IDE-style split panes and tiling windows rather than a free two-dimensional dashboard grid
@dnd-kit/corenpmChoose it when you need accessible custom drag-and-drop behavior but want to own all grid placement and resizing logic
golden-layoutnpmChoose it for dockable, nested, multi-window application panels instead of row-and-column dashboard cards