UNPKG

react-grid-layout

Version:

A draggable and resizable grid layout with responsive breakpoints, for React.

1,314 lines (1,074 loc) 41.7 kB
# React-Grid-Layout [![npm package](https://img.shields.io/npm/v/react-grid-layout.svg?style=flat-square)](https://www.npmjs.org/package/react-grid-layout) [![npm downloads](https://img.shields.io/npm/dt/react-grid-layout.svg?maxAge=2592000)]() React-Grid-Layout is a grid layout system much like [Packery](http://packery.metafizzy.co/) or [Gridster](http://dsmorse.github.io/gridster.js/), for React. Unlike those systems, it is responsive and supports breakpoints. Breakpoint layouts can be provided by the user or autogenerated. RGL is React-only and does not require jQuery. ![BitMEX UI](http://i.imgur.com/oo1NT6c.gif) > GIF from production usage on [BitMEX.com](https://www.bitmex.com) [**[Demo](https://react-grid-layout.github.io/react-grid-layout/) | [Changelog](/CHANGELOG.md) | [CodeSandbox Editable demo](https://codesandbox.io/p/sandbox/5ywf7c)**] ## Table of Contents - [What's New in v2](#whats-new-in-v2) - [Migrating from v1](#migrating-from-v1) - [Demos](#demos) - [Features](#features) - [Installation](#installation) - [Quick Start](#quick-start) - [Responsive Usage](#responsive-usage) - [Providing Grid Width](#providing-grid-width) - [Hooks API](#hooks-api) - [API Reference](#api-reference) - [Extending: Custom Compactors & Position Strategies](#extending-custom-compactors--position-strategies) - [Extras](#extras) - [Performance](#performance) - [Contribute](#contribute) ## What's New in v2 Version 2 is a complete TypeScript rewrite with a modernized API: - **Full TypeScript support** - First-class types, no more `@types/react-grid-layout` - **React Hooks** - New `useContainerWidth`, `useGridLayout`, and `useResponsiveLayout` hooks - **Composable Configuration** - Group related props into focused interfaces: - `gridConfig` - cols, rowHeight, margin, padding - `dragConfig` - enable, handle, cancel, bounded - `resizeConfig` - enable, handles - `positionStrategy` - transform vs absolute positioning - `compactor` - vertical, horizontal, or custom algorithms - **Modular architecture** - Import only what you need: - `react-grid-layout` - React components and hooks (v2 API) - `react-grid-layout/core` - Pure layout algorithms (framework-agnostic) - `react-grid-layout/legacy` - v1 flat props API for migration - `react-grid-layout/extras` - Optional components like `GridBackground` - **Smaller bundle** - Tree-shakeable ESM and CJS builds ### Breaking Changes See the [RFC](./rfcs/0001-v2-typescript-rewrite.md#breaking-changes-in-v2) for detailed migration examples. | Change | Description | | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | [`width` prop required](./rfcs/0001-v2-typescript-rewrite.md#breaking-changes-in-v2) | Use `useContainerWidth` hook or provide your own measurement | | [`onDragStart` threshold](./rfcs/0001-v2-typescript-rewrite.md#1-ondragstart-no-longer-fires-on-click-only-events) | Now fires after 3px movement, not on mousedown. Use `onMouseDown` for immediate response | | [Immutable callbacks](./rfcs/0001-v2-typescript-rewrite.md#2-immutable-callback-parameters) | Callback parameters are read-only. Use `onLayoutChange` or constraints instead of mutation | | [`data-grid` in legacy only](./rfcs/0001-v2-typescript-rewrite.md#3-data-grid-prop-only-available-in-legacy-wrapper) | v2 requires explicit `layout` prop. Use legacy wrapper for `data-grid` | | [Pluggable compaction](./rfcs/0001-v2-typescript-rewrite.md#4-pluggable-compaction-algorithms) | Compaction is now pluggable via `Compactor` interface. Optional fast O(n log n) algorithm in `/extras` | | UMD bundle removed | Use a bundler (Vite, webpack, esbuild) | | `verticalCompact` removed | Use `compactType={null}` or `compactor={noCompactor}` | ## Migrating from v1 **Quick migration** - change your import to use the legacy wrapper: ```diff - import GridLayout, { Responsive, WidthProvider } from 'react-grid-layout'; + import GridLayout, { Responsive, WidthProvider } from 'react-grid-layout/legacy'; ``` This provides **100% runtime API compatibility** with v1. **TypeScript users**: If you were using `@types/react-grid-layout`, note that v2 includes its own types with some naming changes: | Old (`@types/react-grid-layout`) | New (v2) | Notes | | -------------------------------- | ------------------- | ----------------------- | | `RGL.Layout` | `LayoutItem` | Single grid item | | `RGL.Layout[]` | `Layout` | Array of items | | `RGL.Layouts` | `ResponsiveLayouts` | Breakpoint layout map | ```diff - import RGL from 'react-grid-layout'; - const item: RGL.Layout = { i: 'a', x: 0, y: 0, w: 1, h: 1 }; - const layouts: RGL.Layouts = { lg: [item] }; + import { LayoutItem, ResponsiveLayouts } from 'react-grid-layout/legacy'; + const item: LayoutItem = { i: 'a', x: 0, y: 0, w: 1, h: 1 }; + const layouts: ResponsiveLayouts = { lg: [item] }; ``` **Full migration** - adopt the v2 API for new features and better tree-shaking: ```typescript import ReactGridLayout, { useContainerWidth, verticalCompactor } from 'react-grid-layout'; function MyGrid() { const { width, containerRef, mounted } = useContainerWidth(); return ( <div ref={containerRef}> {mounted && ( <ReactGridLayout width={width} layout={layout} gridConfig={{ cols: 12, rowHeight: 30 }} dragConfig={{ enabled: true, handle: '.handle' }} compactor={verticalCompactor} > {children} </ReactGridLayout> )} </div> ); } ``` | Use Case | Recommendation | | -------------------- | ---------------------------------- | | Existing v1 codebase | `react-grid-layout/legacy` | | New project | v2 API with hooks | | Custom compaction | v2 with custom `Compactor` | | SSR | v2 with `measureBeforeMount: true` | ## Demos 1. [Showcase](https://react-grid-layout.github.io/react-grid-layout/examples/00-showcase.html) 1. [Basic](https://react-grid-layout.github.io/react-grid-layout/examples/01-basic.html) 1. [No Dragging/Resizing (Layout Only)](https://react-grid-layout.github.io/react-grid-layout/examples/02-no-dragging.html) 1. [Messy Layout Autocorrect](https://react-grid-layout.github.io/react-grid-layout/examples/03-messy.html) 1. [Layout Defined on Children](https://react-grid-layout.github.io/react-grid-layout/examples/04-grid-property.html) 1. [Static Elements](https://react-grid-layout.github.io/react-grid-layout/examples/05-static-elements.html) 1. [Adding/Removing Elements](https://react-grid-layout.github.io/react-grid-layout/examples/06-dynamic-add-remove.html) 1. [Saving Layout to LocalStorage](https://react-grid-layout.github.io/react-grid-layout/examples/07-localstorage.html) 1. [Saving a Responsive Layout to LocalStorage](https://react-grid-layout.github.io/react-grid-layout/examples/08-localstorage-responsive.html) 1. [Minimum and Maximum Width/Height](https://react-grid-layout.github.io/react-grid-layout/examples/09-min-max-wh.html) 1. [Dynamic Minimum and Maximum Width/Height](https://react-grid-layout.github.io/react-grid-layout/examples/10-dynamic-min-max-wh.html) 1. [Toolbox](https://react-grid-layout.github.io/react-grid-layout/examples/11-toolbox.html) 1. [Drag From Outside](https://react-grid-layout.github.io/react-grid-layout/examples/12-drag-from-outside.html) 1. [Bounded Layout](https://react-grid-layout.github.io/react-grid-layout/examples/13-bounded.html) 1. [Responsive Bootstrap-style Layout](https://react-grid-layout.github.io/react-grid-layout/examples/14-responsive-bootstrap-style.html) 1. [Scaled Containers](https://react-grid-layout.github.io/react-grid-layout/examples/15-scale.html) 1. [Allow Overlap](https://react-grid-layout.github.io/react-grid-layout/examples/16-allow-overlap.html) 1. [All Resizable Handles](https://react-grid-layout.github.io/react-grid-layout/examples/17-resizable-handles.html) 1. [Compactor Showcase](https://react-grid-layout.github.io/react-grid-layout/examples/18-compactors.html) 1. [Pluggable Constraints](https://react-grid-layout.github.io/react-grid-layout/examples/19-constraints.html) 1. [Aspect Ratio Constraints](https://react-grid-layout.github.io/react-grid-layout/examples/20-aspect-ratio.html) 1. [Custom Constraints](https://react-grid-layout.github.io/react-grid-layout/examples/21-custom-constraints.html) #### Projects Using React-Grid-Layout - [Basedash](https://www.basedash.com) - [BitMEX](https://www.bitmex.com/) - [AWS CloudFront Dashboards](https://aws.amazon.com/blogs/aws/cloudwatch-dashboards-create-use-customized-metrics-views/) - [Grafana](https://grafana.com/) - [Metabase](http://www.metabase.com/) - [HubSpot](http://www.hubspot.com) - [Kibana](https://www.elastic.co/products/kibana) - [Monday](https://support.monday.com/hc/en-us/articles/360002187819-What-are-the-Dashboards-) _Know of others? Create a PR to let me know!_ ## Features - 100% React - no jQuery - Full TypeScript support - Compatible with server-rendered apps - Draggable widgets - Resizable widgets - Static widgets - Configurable packing: horizontal, vertical, or off - Bounds checking for dragging and resizing - Widgets may be added or removed without rebuilding grid - Layout can be serialized and restored - Responsive breakpoints - Separate layouts per responsive breakpoint - Grid Items placed using CSS Transforms - Compatibility with `<React.StrictMode>` | Version | Compatibility | | --------- | --------------------- | | >= 2.0.0 | React 18+, TypeScript | | >= 0.17.0 | React 16 & 17 | ## Installation ```bash npm install react-grid-layout ``` Include the stylesheets in your application: ```js import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; ``` Or link them directly: ```html <link rel="stylesheet" href="/node_modules/react-grid-layout/css/styles.css" /> <link rel="stylesheet" href="/node_modules/react-resizable/css/styles.css" /> ``` ## Quick Start ```tsx import ReactGridLayout, { useContainerWidth } from "react-grid-layout"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; function MyGrid() { const { width, containerRef, mounted } = useContainerWidth(); const layout = [ { i: "a", x: 0, y: 0, w: 1, h: 2, static: true }, { i: "b", x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 }, { i: "c", x: 4, y: 0, w: 1, h: 2 } ]; return ( <div ref={containerRef}> {mounted && ( <ReactGridLayout layout={layout} width={width} gridConfig={{ cols: 12, rowHeight: 30 }} > <div key="a">a</div> <div key="b">b</div> <div key="c">c</div> </ReactGridLayout> )} </div> ); } ``` You can also define layout on children using `data-grid`: ```tsx <ReactGridLayout width={width} gridConfig={{ cols: 12, rowHeight: 30 }}> <div key="a" data-grid={{ x: 0, y: 0, w: 1, h: 2, static: true }}> a </div> <div key="b" data-grid={{ x: 1, y: 0, w: 3, h: 2 }}> b </div> <div key="c" data-grid={{ x: 4, y: 0, w: 1, h: 2 }}> c </div> </ReactGridLayout> ``` ## Responsive Usage Use `Responsive` for automatic breakpoint handling: ```tsx import { Responsive, useContainerWidth } from "react-grid-layout"; function MyResponsiveGrid() { const { width, containerRef, mounted } = useContainerWidth(); const layouts = { lg: [{ i: "1", x: 0, y: 0, w: 2, h: 2 }], md: [{ i: "1", x: 0, y: 0, w: 2, h: 2 }] }; return ( <div ref={containerRef}> {mounted && ( <Responsive layouts={layouts} breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} width={width} > <div key="1">1</div> <div key="2">2</div> <div key="3">3</div> </Responsive> )} </div> ); } ``` ## Providing Grid Width The `width` prop is required. You have several options: ### Option 1: useContainerWidth Hook (Recommended) ```tsx import ReactGridLayout, { useContainerWidth } from "react-grid-layout"; function MyGrid() { const { width, containerRef, mounted } = useContainerWidth(); return ( <div ref={containerRef}> {mounted && <ReactGridLayout width={width}>...</ReactGridLayout>} </div> ); } ``` ### Option 2: Fixed Width ```tsx <ReactGridLayout width={1200}>...</ReactGridLayout> ``` ### Option 3: CSS Container Queries or ResizeObserver Use any width measurement library like [react-sizeme](https://github.com/ctrlplusb/react-sizeme) or your own ResizeObserver implementation. ### Option 4: Legacy WidthProvider HOC For backwards compatibility, you can still use `WidthProvider`: ```tsx import ReactGridLayout, { WidthProvider } from "react-grid-layout/legacy"; const GridLayoutWithWidth = WidthProvider(ReactGridLayout); function MyGrid() { return <GridLayoutWithWidth>...</GridLayoutWithWidth>; } ``` ## Hooks API The v2 API provides three hooks for different use cases. Choose based on your needs: | Hook | Use When | | --------------------- | -------------------------------------------------------------------- | | `useContainerWidth` | You need responsive width measurement (most common) | | `useGridLayout` | You're building a custom grid component or need direct state control | | `useResponsiveLayout` | You're building a custom responsive grid with breakpoint logic | ### useContainerWidth Observes container width using ResizeObserver and provides reactive width updates. This is the recommended way to provide width to the grid. **Why use it instead of WidthProvider?** - Hooks are more composable and easier to test - No HOC wrapper means simpler component tree - Explicit control over when to render (via `mounted`) - Works better with SSR ```tsx import { useContainerWidth } from "react-grid-layout"; function MyGrid() { const { width, containerRef, mounted, measureWidth } = useContainerWidth({ measureBeforeMount: false, // Set true for SSR initialWidth: 1280 // Width before first measurement }); return ( <div ref={containerRef}>{mounted && <ReactGridLayout width={width} />}</div> ); } ``` **Type Definitions:** ```ts interface UseContainerWidthOptions { /** Delay render until width is measured. Useful for SSR. Default: false */ measureBeforeMount?: boolean; /** Initial width before measurement. Default: 1280 */ initialWidth?: number; } interface UseContainerWidthResult { /** Current container width in pixels */ width: number; /** Whether the container has been measured at least once */ mounted: boolean; /** Ref to attach to the container element */ containerRef: RefObject<HTMLDivElement | null>; /** Manually trigger a width measurement */ measureWidth: () => void; } ``` ### useGridLayout Core layout state management hook. Use this when you need direct control over drag/resize/drop state, or when building a custom grid component. **Why use it instead of the component?** - Full control over layout state and updates - Access to drag/resize/drop state for custom UIs - Can integrate with external state management - Build headless grid implementations ```tsx import { useGridLayout, horizontalCompactor } from "react-grid-layout"; function CustomGrid({ initialLayout }) { const { layout, setLayout, dragState, resizeState, onDragStart, onDrag, onDragStop, onResizeStart, onResize, onResizeStop, containerHeight, isInteracting, compactor } = useGridLayout({ layout: initialLayout, cols: 12, compactor: horizontalCompactor, // default is verticalCompactor onLayoutChange: newLayout => console.log("Layout changed:", newLayout) }); // Access drag state for custom placeholder rendering const placeholder = dragState.activeDrag; // Check if any interaction is happening if (isInteracting) { // Disable other UI during drag/resize } return ( <div style={{ height: containerHeight * rowHeight }}> {layout.map(item => ( <div key={item.i} onMouseDown={() => onDragStart(item.i, item.x, item.y)} > {item.i} </div> ))} {placeholder && <div className="placeholder" />} </div> ); } ``` **Type Definitions:** ```ts interface UseGridLayoutOptions { /** Initial layout */ layout: Layout; /** Number of columns */ cols: number; /** Block movement into occupied space instead of pushing items */ preventCollision?: boolean; /** Called when layout changes */ onLayoutChange?: (layout: Layout) => void; /** Compactor for layout compaction (default: verticalCompactor) */ compactor?: Compactor; } interface UseGridLayoutResult { /** Current layout */ layout: Layout; /** Set layout directly */ setLayout: (layout: Layout) => void; /** Current drag state (activeDrag, oldDragItem, oldLayout) */ dragState: DragState; /** Current resize state (resizing, oldResizeItem, oldLayout) */ resizeState: ResizeState; /** Current drop state (droppingDOMNode, droppingPosition) */ dropState: DropState; /** Start dragging an item */ onDragStart: (itemId: string, x: number, y: number) => LayoutItem | null; /** Update drag position */ onDrag: (itemId: string, x: number, y: number) => void; /** Stop dragging */ onDragStop: (itemId: string, x: number, y: number) => void; /** Start resizing an item */ onResizeStart: (itemId: string) => LayoutItem | null; /** Update resize dimensions */ onResize: ( itemId: string, w: number, h: number, x?: number, y?: number ) => void; /** Stop resizing */ onResizeStop: (itemId: string, w: number, h: number) => void; /** Handle external drag over */ onDropDragOver: ( droppingItem: LayoutItem, position: DroppingPosition ) => void; /** Handle external drag leave */ onDropDragLeave: () => void; /** Complete external drop */ onDrop: (droppingItem: LayoutItem) => void; /** Container height in grid rows */ containerHeight: number; /** Whether any drag/resize/drop is active */ isInteracting: boolean; /** The compactor being used */ compactor: Compactor; } ``` ### useResponsiveLayout Manages responsive breakpoints and generates layouts for different screen sizes. Use this when building a custom responsive grid. **Why use it instead of the Responsive component?** - Direct access to current breakpoint - Control over layout generation for new breakpoints - Can update layouts for specific breakpoints - Build custom breakpoint UIs ```tsx import { useContainerWidth, useResponsiveLayout } from "react-grid-layout"; function CustomResponsiveGrid() { const { width, containerRef, mounted } = useContainerWidth(); const { layout, // Current layout for active breakpoint layouts, // All layouts by breakpoint breakpoint, // Current active breakpoint ('lg', 'md', etc.) cols, // Column count for current breakpoint setLayoutForBreakpoint, setLayouts, sortedBreakpoints } = useResponsiveLayout({ width, breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }, cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }, layouts: { lg: [{ i: "1", x: 0, y: 0, w: 2, h: 2 }], md: [{ i: "1", x: 0, y: 0, w: 3, h: 2 }] }, // compactor: verticalCompactor (default) onBreakpointChange: (bp, cols) => console.log(`Now at ${bp} (${cols} cols)`), onLayoutChange: (layout, allLayouts) => saveToServer(allLayouts) }); // Show current breakpoint in UI return ( <div ref={containerRef}> <div> Current breakpoint: {breakpoint} ({cols} columns) </div> {mounted && ( <GridLayout width={width} cols={cols} layout={layout}> {/* children */} </GridLayout> )} </div> ); } ``` **Type Definitions:** ```ts interface UseResponsiveLayoutOptions<B extends string = DefaultBreakpoints> { /** Current container width */ width: number; /** Breakpoint definitions (name → min-width). Default: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0} */ breakpoints?: Record<B, number>; /** Column counts per breakpoint. Default: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2} */ cols?: Record<B, number>; /** Layouts for each breakpoint */ layouts?: Partial<Record<B, Layout>>; /** Compactor for layout compaction (default: verticalCompactor) */ compactor?: Compactor; /** Called when breakpoint changes */ onBreakpointChange?: (newBreakpoint: B, cols: number) => void; /** Called when layout changes */ onLayoutChange?: (layout: Layout, layouts: Record<B, Layout>) => void; /** Called when width changes */ onWidthChange?: ( width: number, margin: [number, number], cols: number, padding: [number, number] | null ) => void; } interface UseResponsiveLayoutResult<B extends string = DefaultBreakpoints> { /** Current layout for the active breakpoint */ layout: Layout; /** All layouts by breakpoint */ layouts: Partial<Record<B, Layout>>; /** Current active breakpoint */ breakpoint: B; /** Column count for the current breakpoint */ cols: number; /** Update layout for a specific breakpoint */ setLayoutForBreakpoint: (breakpoint: B, layout: Layout) => void; /** Update all layouts */ setLayouts: (layouts: Partial<Record<B, Layout>>) => void; /** Sorted array of breakpoint names (smallest to largest) */ sortedBreakpoints: B[]; } type DefaultBreakpoints = "lg" | "md" | "sm" | "xs" | "xxs"; ``` ## API Reference ### ReactGridLayout Props The v2 API uses composable configuration interfaces for cleaner prop organization: ```ts interface ReactGridLayoutProps { // Required children: React.ReactNode; width: number; // Container width in pixels // Configuration interfaces (see below for details) gridConfig?: Partial<GridConfig>; // Grid measurement settings dragConfig?: Partial<DragConfig>; // Drag behavior settings resizeConfig?: Partial<ResizeConfig>; // Resize behavior settings dropConfig?: Partial<DropConfig>; // External drop settings positionStrategy?: PositionStrategy; // CSS positioning strategy compactor?: Compactor; // Layout compaction strategy // Layout data layout?: Layout; // Layout definition droppingItem?: LayoutItem; // Item configuration when dropping from outside // Container autoSize?: boolean; // Auto-size container height (default: true) className?: string; style?: React.CSSProperties; innerRef?: React.Ref<HTMLDivElement>; // Callbacks onLayoutChange?: (layout: Layout) => void; onDragStart?: EventCallback; onDrag?: EventCallback; onDragStop?: EventCallback; onResizeStart?: EventCallback; onResize?: EventCallback; onResizeStop?: EventCallback; onDrop?: (layout: Layout, item: LayoutItem | undefined, e: Event) => void; onDropDragOver?: (e: DragEvent) => { w?: number; h?: number } | false | void; } ``` ### GridConfig Grid measurement configuration: ```ts interface GridConfig { cols: number; // Number of columns (default: 12) rowHeight: number; // Row height in pixels (default: 150) margin: [number, number]; // [x, y] margin between items (default: [10, 10]) containerPadding: [number, number] | null; // Container padding (default: null, uses margin) maxRows: number; // Maximum rows (default: Infinity) } ``` ### DragConfig Drag behavior configuration: ```ts interface DragConfig { enabled: boolean; // Enable dragging (default: true) bounded: boolean; // Keep items within container (default: false) handle?: string; // CSS selector for drag handle cancel?: string; // CSS selector to cancel dragging threshold: number; // Pixels to move before drag starts (default: 3) } ``` ### ResizeConfig Resize behavior configuration: ```ts interface ResizeConfig { enabled: boolean; // Enable resizing (default: true) handles: ResizeHandleAxis[]; // Handle positions (default: ['se']) handleComponent?: React.ReactNode | ((axis, ref) => React.ReactNode); } ``` ### DropConfig External drop configuration: ```ts interface DropConfig { enabled: boolean; // Allow external drops (default: false) defaultItem: { w: number; h: number }; // Default size (default: { w: 1, h: 1 }) onDragOver?: (e: DragEvent) => { w?: number; h?: number } | false | void; } ``` ### PositionStrategy CSS positioning strategy. Built-in options: ```ts import { transformStrategy, // Default: use CSS transforms absoluteStrategy, // Use top/left positioning createScaledStrategy // For scaled containers } from "react-grid-layout/core"; // Example: scaled container <div style={{ transform: 'scale(0.5)' }}> <ReactGridLayout positionStrategy={createScaledStrategy(0.5)} ... /> </div> ``` ### Compactor Layout compaction strategy. Built-in options: ```ts import { verticalCompactor, // Default: compact items upward horizontalCompactor, // Compact items leftward noCompactor, // No compaction (free positioning) getCompactor // Factory: getCompactor('vertical', allowOverlap, preventCollision) } from "react-grid-layout/core"; ``` ### ResponsiveGridLayout Props Extends `GridLayoutProps` with responsive-specific props: ```ts interface ResponsiveGridLayoutProps<B extends string = string> { // Responsive configuration breakpoint?: B; // Current breakpoint (auto-detected) breakpoints?: Record<B, number>; // Breakpoint definitions (default: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}) cols?: Record<B, number>; // Columns per breakpoint (default: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}) layouts?: Record<B, Layout>; // Layouts per breakpoint // Can be fixed or per-breakpoint margin?: [number, number] | Partial<Record<B, [number, number]>>; containerPadding?: | [number, number] | Partial<Record<B, [number, number] | null>> | null; // Callbacks onBreakpointChange?: (newBreakpoint: B, cols: number) => void; onLayoutChange?: (layout: Layout, layouts: Record<B, Layout>) => void; onWidthChange?: ( width: number, margin: [number, number], cols: number, padding: [number, number] | null ) => void; } ``` ### Layout Item ```ts interface LayoutItem { i: string; // Unique identifier (must match child key) x: number; // X position in grid units y: number; // Y position in grid units w: number; // Width in grid units h: number; // Height in grid units minW?: number; // Minimum width (default: 0) maxW?: number; // Maximum width (default: Infinity) minH?: number; // Minimum height (default: 0) maxH?: number; // Maximum height (default: Infinity) static?: boolean; // If true, not draggable or resizable isDraggable?: boolean; // Override grid isDraggable isResizable?: boolean; // Override grid isResizable isBounded?: boolean; // Override grid isBounded resizeHandles?: Array<"s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne">; } ``` ### Core Utilities Import pure layout functions from `react-grid-layout/core`: ```ts import { verticalCompactor, horizontalCompactor, noCompactor, getCompactor, moveElement, collides, getFirstCollision, validateLayout // ... and more } from "react-grid-layout/core"; ``` > **Note**: The `compact()` function is not exported. Use compactors instead: `verticalCompactor.compact(layout, cols)` or get one via `getCompactor('vertical')`. ## Extending: Custom Compactors & Position Strategies ### Creating a Custom Compactor Compactors control how items are arranged after drag/resize. Create your own for custom layouts like masonry, gravity, or shelf-packing. **The Compactor Interface:** ```ts interface Compactor { /** Identifies the compaction type */ type: "vertical" | "horizontal" | null | string; /** * Whether items can overlap each other. * * When true: * - Items can be placed on top of other items * - Dragging into another item does NOT push it away * - Compaction is skipped after drag/resize * * Use for: layered dashboards, free-form layouts */ allowOverlap: boolean; /** * Whether to block movement that would cause collision. * * When true (and allowOverlap is false): * - Dragging into another item is blocked (item snaps back) * - Other items are NOT pushed away * - Only affects movement, not compaction * * Use with noCompactor for: fixed grids, slot-based layouts * * Note: Has no effect when allowOverlap is true. */ preventCollision?: boolean; /** * Compact the entire layout. * Called after any layout change to fill gaps. * * @param layout - Array of layout items (clone before mutating!) * @param cols - Number of grid columns * @returns New compacted layout */ compact(layout: Layout, cols: number): Layout; } ``` **Example: Gravity Compactor (items fall to bottom)** ```ts import { cloneLayout, cloneLayoutItem, getStatics, bottom } from "react-grid-layout/core"; const gravityCompactor: Compactor = { type: "gravity", allowOverlap: false, compact(layout, cols) { const statics = getStatics(layout); const maxY = 100; // arbitrary max height const out = []; // Sort by Y descending (process bottom items first) const sorted = [...layout].sort((a, b) => b.y - a.y); for (const item of sorted) { const l = cloneLayoutItem(item); if (!l.static) { // Move down as far as possible while (l.y < maxY && !collides(l, statics)) { l.y++; } l.y--; // Back up one } out.push(l); } return out; } }; // Usage <GridLayout compactor={gravityCompactor} /> ``` **Example: Single Row Compactor (horizontal shelf)** ```ts const singleRowCompactor: Compactor = { type: "shelf", allowOverlap: false, compact(layout, cols) { let x = 0; const out = []; // Sort by original X position const sorted = [...layout].sort((a, b) => a.x - b.x); for (const item of sorted) { const l = cloneLayoutItem(item); if (!l.static) { l.x = x; l.y = 0; // All items on row 0 x += l.w; // Wrap to next row if overflow if (x > cols) { l.x = 0; x = l.w; } } out.push(l); } return out; } }; ``` **Using Helper Functions:** The core module exports helpers for building compactors: ```ts import { resolveCompactionCollision, // Move items to resolve overlaps compactItemVertical, // Compact one item upward compactItemHorizontal, // Compact one item leftward getFirstCollision, // Find first collision collides, // Check if two items collide getStatics, // Get static items from layout cloneLayout, // Clone layout array cloneLayoutItem // Clone single item } from "react-grid-layout/core"; ``` ### Creating a Custom Position Strategy Position strategies control how items are positioned via CSS. Create custom strategies for special transform handling. **The PositionStrategy Interface:** ```ts interface PositionStrategy { /** Type identifier */ type: "transform" | "absolute" | string; /** Scale factor for coordinate calculations */ scale: number; /** * Generate CSS styles for positioning an item. * * @param pos - Position with top, left, width, height in pixels * @returns CSS properties object */ calcStyle(pos: Position): React.CSSProperties; /** * Calculate drag position from mouse coordinates. * Used during drag to convert screen coords to grid coords. * * @param clientX - Mouse X position * @param clientY - Mouse Y position * @param offsetX - Offset from item left edge * @param offsetY - Offset from item top edge * @returns Calculated left/top position */ calcDragPosition( clientX: number, clientY: number, offsetX: number, offsetY: number ): { left: number; top: number }; } ``` **Example: Rotated Container Strategy** ```ts const createRotatedStrategy = (angleDegrees: number): PositionStrategy => { const angleRad = (angleDegrees * Math.PI) / 180; const cos = Math.cos(angleRad); const sin = Math.sin(angleRad); return { type: "rotated", scale: 1, calcStyle(pos) { // Apply rotation to position const rotatedX = pos.left * cos - pos.top * sin; const rotatedY = pos.left * sin + pos.top * cos; return { transform: `translate(${rotatedX}px, ${rotatedY}px)`, width: `${pos.width}px`, height: `${pos.height}px`, position: "absolute" }; }, calcDragPosition(clientX, clientY, offsetX, offsetY) { // Reverse the rotation for drag calculations const x = clientX - offsetX; const y = clientY - offsetY; return { left: x * cos + y * sin, top: -x * sin + y * cos }; } }; }; // Usage: grid inside a rotated container <div style={{ transform: 'rotate(45deg)' }}> <GridLayout positionStrategy={createRotatedStrategy(45)} /> </div> ``` **Example: 3D Perspective Strategy** ```ts const create3DStrategy = ( perspective: number, rotateX: number ): PositionStrategy => ({ type: "3d", scale: 1, calcStyle(pos) { return { transform: ` perspective(${perspective}px) rotateX(${rotateX}deg) translate3d(${pos.left}px, ${pos.top}px, 0) `, width: `${pos.width}px`, height: `${pos.height}px`, position: "absolute", transformStyle: "preserve-3d" }; }, calcDragPosition(clientX, clientY, offsetX, offsetY) { // Adjust for perspective foreshortening const perspectiveFactor = 1 + clientY / perspective; return { left: (clientX - offsetX) / perspectiveFactor, top: (clientY - offsetY) / perspectiveFactor }; } }); ``` ## Extras The `react-grid-layout/extras` entry point provides optional components that extend react-grid-layout. These are tree-shakeable and won't be included in your bundle unless explicitly imported. ### GridBackground Renders an SVG grid background that aligns with GridLayout cells. Use this to visualize the grid structure behind your layout. > Based on [PR #2175](https://github.com/react-grid-layout/react-grid-layout/pull/2175) by [@dmj900501](https://github.com/dmj900501). ```tsx import { GridBackground } from "react-grid-layout/extras"; import ReactGridLayout, { useContainerWidth } from "react-grid-layout"; function MyGrid() { const { width, containerRef, mounted } = useContainerWidth(); return ( <div ref={containerRef} style={{ position: "relative" }}> {mounted && ( <> <GridBackground width={width} cols={12} rowHeight={30} margin={[10, 10]} rows={10} color="#f0f0f0" borderRadius={4} /> <ReactGridLayout width={width} gridConfig={{ cols: 12, rowHeight: 30, margin: [10, 10] }} > {children} </ReactGridLayout> </> )} </div> ); } ``` **Props:** ```ts interface GridBackgroundProps { // Required - must match your GridLayout config width: number; // Container width cols: number; // Number of columns rowHeight: number; // Row height in pixels // Optional margin?: [number, number]; // Gap between cells (default: [10, 10]) containerPadding?: [number, number] | null; // Container padding (default: uses margin) rows?: number | "auto"; // Number of rows to display (default: 10) height?: number; // Used when rows="auto" to calculate row count color?: string; // Cell background color (default: "#e0e0e0") borderRadius?: number; // Cell border radius (default: 4) className?: string; // Additional CSS class style?: React.CSSProperties; // Additional inline styles } ``` ### Fast Compactors For large layouts (200+ items), the standard compactors can become slow due to O(n²) collision resolution. The fast compactors use optimized algorithms with O(n log n) complexity. > Based on the "rising tide" algorithm from [PR #2152](https://github.com/react-grid-layout/react-grid-layout/pull/2152) by [@morris](https://github.com/morris). ```tsx import { fastVerticalCompactor, fastHorizontalCompactor, fastVerticalOverlapCompactor, fastHorizontalOverlapCompactor } from "react-grid-layout/extras"; <ReactGridLayout compactor={fastVerticalCompactor} // or compactor={fastHorizontalCompactor} layout={layout} width={width} />; ``` **Performance Benchmarks:** | Items | Standard Vertical | Fast Vertical | Speedup | | ----- | ----------------- | ------------- | ------- | | 50 | 112 µs | 19 µs | **6x** | | 100 | 203 µs | 36 µs | **6x** | | 200 | 821 µs | 51 µs | **16x** | | 500 | 5.7 ms | 129 µs | **45x** | | Items | Standard Horizontal | Fast Horizontal | Speedup | | ----- | ------------------- | --------------- | ------- | | 50 | 164 µs | 12 µs | **14x** | | 100 | 477 µs | 25 µs | **19x** | | 200 | 1.1 ms | 42 µs | **26x** | | 500 | 9.5 ms | 128 µs | **74x** | **Correctness:** The fast compactors produce layouts identical to the standard compactors: - **Vertical**: 0% height difference on deterministic 100-item layouts - **Horizontal**: 0% width difference on deterministic 100-item layouts - Both pass all correctness tests: no overlaps, idempotent, static item handling **When to use:** - Use fast compactors for dashboards with 200+ widgets - For smaller layouts (<100 items), standard compactors work equally well - Both standard and fast compactors produce valid, non-overlapping layouts ### calcGridCellDimensions (Core Utility) For building custom grid overlays or backgrounds, use the `calcGridCellDimensions` utility from `react-grid-layout/core`: ```ts import { calcGridCellDimensions } from "react-grid-layout/core"; const dims = calcGridCellDimensions({ width: 1200, cols: 12, rowHeight: 30, margin: [10, 10], containerPadding: [20, 20] }); // dims = { // cellWidth: 88.33, // Width of each cell // cellHeight: 30, // Height of each cell (= rowHeight) // offsetX: 20, // Left padding // offsetY: 20, // Top padding // gapX: 10, // Horizontal gap between cells // gapY: 10, // Vertical gap between cells // cols: 12, // Column count // containerWidth: 1200 // } ``` This is useful for building custom visualizations, snap-to-grid functionality, or integrating with canvas/WebGL renderers. ## Performance ### Memoize Children The grid compares children by reference. Memoize them for better performance: ```tsx function MyGrid({ count, width }) { const children = useMemo(() => { return Array.from({ length: count }, (_, i) => ( <div key={i} data-grid={{ x: i % 12, y: Math.floor(i / 12), w: 1, h: 1 }} /> )); }, [count]); return ( <ReactGridLayout width={width} gridConfig={{ cols: 12 }}> {children} </ReactGridLayout> ); } ``` ### Avoid Creating Components in Render (Legacy WidthProvider) If using the legacy WidthProvider HOC, don't create the component during render: ```tsx import ReactGridLayout, { WidthProvider } from "react-grid-layout/legacy"; // Bad - creates new component every render function MyGrid() { const GridLayoutWithWidth = WidthProvider(ReactGridLayout); return <GridLayoutWithWidth>...</GridLayoutWithWidth>; } // Good - create once outside or with useMemo const GridLayoutWithWidth = WidthProvider(ReactGridLayout); function MyGrid() { return <GridLayoutWithWidth>...</GridLayoutWithWidth>; } ``` With the v2 API, use `useContainerWidth` hook instead to avoid this issue entirely. ## Custom Child Components Grid children must forward refs and certain props: ```tsx const CustomItem = forwardRef<HTMLDivElement, CustomItemProps>( ( { style, className, onMouseDown, onMouseUp, onTouchEnd, children, ...props }, ref ) => { return ( <div ref={ref} style={style} className={className} onMouseDown={onMouseDown} onMouseUp={onMouseUp} onTouchEnd={onTouchEnd} > {children} </div> ); } ); ``` ## Contribute If you have a feature request, please add it as an issue or make a pull request. If you have a bug to report, please reproduce the bug in [CodeSandbox](https://codesandbox.io/p/sandbox/5ywf7c) to help us easily isolate it.