npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-dashboard-grid

v1.0.4

Published

A flexible, themeable React dashboard library with drag-and-drop layout, a rich widget library, and a fully functional Add Widget modal. Built on top of react-grid-layout, it provides a powerful and customizable solution for building complex dashboards wi

Readme

react-dashboard-grid

npm downloads npm bundle size

react-dashboard-grid is a React component library for building interactive, data-rich dashboards. It ships with a complete set of built-in widgets - stat cards, charts, tables, gauges, calendars, and more - alongside a drag-and-drop layout engine, a live theming system, and a modal for adding widgets at runtime. Every widget is sized and positioned on a 12-column grid, so dashboards compose predictably whether you're building an analytics product, an ops tool, or an internal admin panel. The library is written in TypeScript and exports full type definitions.

Demo

Check out the interactive components on Storybook.


Table of Contents


Installation

npm install react-dashboard-grid

Quick Start

import { Dashboard } from "react-dashboard-grid";
import type { WidgetItem } from "react-dashboard-grid";

const items: WidgetItem[] = [
  {
    id: "revenue",
    type: "stat",
    size: "small",
    data: {
      title: "Revenue",
      value: "$48,295",
      change: "+12.4%",
      changeType: "up",
    },
  },
  {
    id: "sales-chart",
    type: "chart",
    size: "medium",
    data: {
      chartType: "line",
      title: "Monthly Sales",
      data: [
        { label: "Jan", value: 30 },
        { label: "Feb", value: 55 },
        { label: "Mar", value: 40 },
        { label: "Apr", value: 70 },
      ],
    },
  },
];

export default function App() {
  return (
    <Dashboard
      title="My Dashboard"
      subtitle="Overview of key metrics"
      items={items}
    />
  );
}

<Dashboard> Props

| Prop | Type | Default | Description | | -------------------- | ---------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | items | WidgetItem[] | required | Initial widget list. Dashboard manages its own state internally — new widgets are appended without requiring parent re-render. | | title | string | | Header title | | subtitle | string | | Header subtitle | | showAddWidget | boolean | true | Show "+ Add Widget" button | | showEditLayout | boolean | true | Show "Edit Layout" toggle | | showSearch | boolean | true | Show search input | | headerActions | DashboardAction[] | | Extra buttons rendered in the header | | onLayoutChange | (layout: WidgetLayoutChange[]) => void | | Fires after drag/resize; receives clean backend-ready payload | | onAddWidget | () => void | | Side-effect callback — fires after a widget is added. Dashboard handles state internally. | | onSearch | (query: string) => void | | Fires on search input change | | theme | DashboardTheme | see defaults | Theme object (see Theming section) | | className | string | | Root element class | | widgetTemplates | WidgetTemplate[] | DEFAULT_TEMPLATES | Templates for the Add Widget modal | | searchLabel | string | "Search widgets..." | Placeholder for search input | | editLabel | string | "Edit Layout" | Edit button label | | editingLabel | string | "Done" | Edit button label when active | | addLabel | string | "+ Add Widget" | Add widget button label | | disableEditingGrid | boolean | false | Suppress grid overlay while editing |

Widget Layout and Positioning

Every WidgetItem accepts an optional layout property that controls where it appears on the grid. The dashboard uses a 12-column grid, and each widget occupies a number of columns determined by its size (or customSize). The layout object lets you pin a widget to a specific column (x) and row (y).

interface WidgetLayout {
  x: number; // Column index (0–11). 0 = left edge.
  y: number; // Row index. 0 = top. Rows are unitless — each unit corresponds to one row-height slot.
}

x is the starting column, zero-indexed from the left edge. A small widget is 3 columns wide, so setting x: 9 places it flush against the right edge of a 12-column grid. A large widget is 12 columns wide, so it always starts at x: 0.

y is the starting row. Row heights are determined by the widget's size: a small widget is 2 row units tall, medium is 3, and large is 4. To place two rows of small widgets without overlap, the second row should start at y: 2.

Here is a worked example from the Storybook showcasing a typical 4-up stat row followed by a chart row:

const items: WidgetItem[] = [
  // Four stat cards across the top — each 3 columns wide, starting at y: 0
  { id: "s1", type: "stat", size: "small", layout: { x: 0, y: 0 }, data: { ... } },
  { id: "s2", type: "stat", size: "small", layout: { x: 3, y: 0 }, data: { ... } },
  { id: "s3", type: "stat", size: "small", layout: { x: 6, y: 0 }, data: { ... } },
  { id: "s4", type: "stat", size: "small", layout: { x: 9, y: 0 }, data: { ... } },

  // Two medium charts on the next row — small widgets are 2 rows tall, so y: 2
  { id: "c1", type: "chart", size: "medium", layout: { x: 0, y: 2 }, data: { ... } },
  { id: "c2", type: "chart", size: "medium", layout: { x: 6, y: 2 }, data: { ... } },
];

If you omit layout, the grid engine places widgets automatically in left-to-right, top-to-bottom order. This is fine for simple dashboards, but you should provide explicit layout values whenever you want consistent positioning — especially if widgets will be added or removed dynamically.

After a user drags or resizes widgets, the updated positions are reported via onLayoutChange, so you can persist them and restore the same positions on next load.

Add Widget Modal

The AddWidgetModal is driven by programmer-supplied templates. Each template includes the complete, pre-configured WidgetItem data (minus id) that will be cloned onto the dashboard when the user clicks "Add to Dashboard".

WidgetTemplate interface

interface WidgetTemplate {
  key: string; // Unique identifier for this template
  label: string; // Display name in the modal
  description: string; // One-sentence description
  component: Omit<WidgetItem, "id">; // The complete, pre-configured widget data
}

Custom templates

import { Dashboard } from "react-dashboard-grid";
import type { WidgetTemplate } from "react-dashboard-grid";

const MY_TEMPLATES: WidgetTemplate[] = [
  {
    key: "monthly-revenue",
    label: "Monthly Revenue",
    description: "Tracks total revenue across the current month.",
    component: {
      type: "stat",
      size: "small",
      data: {
        title: "Monthly Revenue",
        value: "$0",
        change: "vs last month",
        changeType: "neutral",
      },
    },
  },
  {
    key: "conversion-funnel",
    label: "Conversion Funnel",
    description: "Bar chart showing drop-off at each funnel stage.",
    component: {
      type: "chart",
      size: "medium",
      data: {
        chartType: "bar",
        title: "Conversion Funnel",
        data: [
          { label: "Visits", value: 10000 },
          { label: "Signups", value: 4500 },
          { label: "Activated", value: 2100 },
          { label: "Paid", value: 800 },
        ],
      },
    },
  },
  {
    key: "team-members",
    label: "Team List",
    description: "Scrollable list of team members with role badges.",
    component: {
      type: "list",
      size: "medium",
      data: {
        title: "Team Members",
        items: [
          {
            id: "1",
            label: "Alice Johnson",
            badge: "Admin",
            badgeColor: "#6366f1",
          },
          {
            id: "2",
            label: "Bob Chen",
            badge: "Editor",
            badgeColor: "#22c55e",
          },
        ],
      },
    },
  },
];

export default function App() {
  return (
    <Dashboard title="Analytics" items={[]} widgetTemplates={MY_TEMPLATES} />
  );
}

Using DEFAULT_TEMPLATES

If you do not pass widgetTemplates, the modal uses DEFAULT_TEMPLATES which covers every built-in widget type:

import { DEFAULT_TEMPLATES } from "react-dashboard-grid";

const templates = [
  ...DEFAULT_TEMPLATES,
  {
    key: "custom-kpi",
    label: "Custom KPI",
    description: "Your custom KPI widget.",
    component: {
      type: "stat",
      size: "small",
      data: { title: "KPI", value: "—" },
    },
  },
];

Standalone modal usage

import { AddWidgetModal } from "react-dashboard-grid";
import type { WidgetItem } from "react-dashboard-grid";

function MyPage() {
  const [open, setOpen] = useState(false);
  const [widgets, setWidgets] = useState<WidgetItem[]>([]);

  return (
    <>
      <button onClick={() => setOpen(true)}>Add Widget</button>
      <AddWidgetModal
        isOpen={open}
        onClose={() => setOpen(false)}
        onAdd={(widget) => setWidgets((prev) => [...prev, widget])}
        primaryColor="#6366f1"
      />
    </>
  );
}

Widget Reference

stat - Stat Card

Displays a single KPI metric with an optional trend arrow and icon.

{
  id: "revenue",
  type: "stat",
  size: "small",
  data: {
    title: "Monthly Revenue",
    value: "$48,295",
    change: "+12.4% vs last month",
    changeType: "up", // "up" | "down" | "neutral"
    icon: <SomeIcon />, // optional React node
  },
}

StatData props:

| Prop | Type | Description | | ------------ | ----------------------------- | ------------------------- | | title | string | Metric label | | value | string \| number | Main displayed value | | change | string | Optional trend text | | changeType | "up" \| "down" \| "neutral" | Controls arrow and color | | icon | ReactNode | Optional icon (top-right) |

chart - Chart Card

Supports 5 chart types via a single unified chartType discriminator.

Line Chart

{
  id: "sales-trend",
  type: "chart",
  size: "medium",
  data: {
    chartType: "line",
    title: "Sales Trend",
    subtitle: "Last 6 months",
    data: [
      { label: "Jan", value: 400, value2: 300 },
      { label: "Feb", value: 600, value2: 450 },
    ],
    showGrid: true,
    showLegend: true,
    seriesLabels: { value: "Revenue", value2: "Costs" },
    colors: { primary: "#6366f1", secondary: "#f59e0b" },
  },
}

Bar Chart

{
  id: "quarterly",
  type: "chart",
  size: "medium",
  data: {
    chartType: "bar",
    title: "Quarterly Performance",
    stacked: false,
    data: [
      { label: "Q1", value: 120, value2: 80 },
      { label: "Q2", value: 180, value2: 110 },
    ],
  },
}

Area Chart

{
  id: "traffic",
  type: "chart",
  size: "medium",
  data: {
    chartType: "area",
    title: "Site Traffic",
    stacked: false,
    data: [{ label: "Mon", value: 500 }, { label: "Tue", value: 750 }],
  },
}

Pie / Donut Chart

{
  id: "channels",
  type: "chart",
  size: "medium",
  data: {
    chartType: "donut", // or "pie"
    title: "Traffic Sources",
    showLegend: true,
    data: [
      { label: "Organic", value: 45, color: "#6366f1" },
      { label: "Direct", value: 30, color: "#22c55e" },
      { label: "Referral", value: 25, color: "#f59e0b" },
    ],
  },
}

Histogram

{
  id: "distribution",
  type: "chart",
  size: "medium",
  data: {
    chartType: "histogram",
    title: "Score Distribution",
    data: [
      { label: "0–20", value: 5 },
      { label: "20–40", value: 15 },
      { label: "40–60", value: 32 },
      { label: "60–80", value: 28 },
      { label: "80–100", value: 12 },
    ],
  },
}

ChartData shared props:

| Prop | Type | Description | | -------------- | ------------------ | --------------------------------------------- | | title | string | Card title | | subtitle | string | Optional subtitle | | data | ChartDataPoint[] | Array of { label, value, value2?, value3? } | | colors | ChartColors | Override series colors | | showGrid | boolean | Show grid lines (default: true) | | showLegend | boolean | Show legend (default: false) | | seriesLabels | object | Labels for multi-series legend |

list - List Card

Scrollable list of labelled rows with optional values, badges, and icons.

{
  id: "top-pages",
  type: "list",
  size: "medium",
  data: {
    title: "Top Pages",
    items: [
      { id: "1", label: "/home", value: "12,400 views", badge: "↑8%", badgeColor: "#22c55e" },
      { id: "2", label: "/pricing", value: "8,200 views", badge: "↓2%", badgeColor: "#ef4444" },
      { id: "3", label: "/docs", value: "5,600 views" },
    ],
  },
}

ListItem props:

| Prop | Type | Description | | ------------ | ----------- | ------------------------- | | id | string | Unique row key | | label | string | Row label | | value | string | Optional right-side value | | badge | string | Optional badge text | | badgeColor | string | Badge background color | | icon | ReactNode | Optional leading icon |

table - Data Table

Sortable, paginated table for dense data.

{
  id: "orders",
  type: "table",
  size: "large",
  data: {
    title: "Recent Orders",
    subtitle: "Last 30 days",
    sortable: true,
    striped: true,
    rowsPerPage: 5,
    columns: [
      { key: "id", label: "Order ID", width: 100 },
      { key: "customer", label: "Customer" },
      { key: "total", label: "Total", align: "right", render: (v) => `$${v}` },
      { key: "status", label: "Status", render: (v) => <StatusBadge status={v} /> },
    ],
    rows: [
      { id: "#1001", customer: "Alice", total: 299, status: "paid" },
      { id: "#1002", customer: "Bob", total: 149, status: "pending" },
    ],
  },
}

TableData props:

| Prop | Type | Description | | ------------- | ----------------------- | -------------------------- | | title | string | Card title | | subtitle | string | Optional subtitle | | columns | TableColumn[] | Column definitions | | rows | Record<string, any>[] | Row data | | sortable | boolean | Enable column sorting | | striped | boolean | Alternate row shading | | rowsPerPage | number | Rows per page (default: 5) |

TableColumn props:

| Prop | Type | Description | | -------- | ------------------------------- | -------------------- | | key | string | Row data key | | label | string | Column header | | width | string \| number | Column width | | align | "left" \| "center" \| "right" | Cell alignment | | render | (value, row) => ReactNode | Custom cell renderer |

gauge - Gauge Card

Semicircle gauge with a percentage fill and needle.

{
  id: "cpu",
  type: "gauge",
  size: "small",
  data: {
    title: "CPU Usage",
    subtitle: "Average across cores",
    value: "73%",
    label: "utilisation",
    percentage: 73,   // 0–100
    changeText: "↑4% from last hour",
    color: "#6366f1", // optional override
  },
}

profile - Profile Card

User card with avatar, name, role badge, and optional stat row.

{
  id: "user-profile",
  type: "profile",
  size: "small",
  data: {
    name: "Sarah Chen",
    email: "[email protected]",
    role: "Senior Engineer",
    badgeColor: "#6366f1",
    avatarUrl: "https://example.com/avatar.jpg",
    // avatarEmoji: "👩‍💻",  // fallback if no URL
    stats: [
      { label: "PRs", value: 142 },
      { label: "Reviews", value: 89 },
      { label: "Issues", value: 24 },
    ],
  },
}

splitstat - Split Progress Card

Main value with a labelled multi-segment stacked progress bar.

{
  id: "disk-usage",
  type: "splitstat",
  size: "small",
  data: {
    title: "Storage Used",
    titleAction: "Manage",
    value: "284",
    unit: "GB",
    minValue: "0 GB",
    maxValue: "500 GB",
    segments: [
      { label: "Documents", percentage: 40, value: "114 GB", color: "#6366f1" },
      { label: "Media", percentage: 35, value: "99 GB", color: "#22c55e" },
      { label: "Backups", percentage: 25, value: "71 GB", color: "#f59e0b" },
    ],
  },
}

calendar - Calendar Card

Monthly calendar with event dot indicators.

{
  id: "schedule",
  type: "calendar",
  size: "medium",
  data: {
    title: "Schedule",
    highlightToday: true,
    primaryColor: "#6366f1",
    onDateSelect: (date) => console.log("Selected:", date),
    events: [
      { date: "2024-04-15", label: "Sprint Review", color: "#6366f1" },
      { date: "2024-04-20", label: "Release", color: "#22c55e" },
      { date: "2024-04-20", label: "Standup", color: "#f59e0b" },
    ],
  },
}

text - Text Block Card

Free-form text or notes widget.

{
  id: "notes",
  type: "text",
  size: "small",
  data: {
    title: "Notes",
    body: "Q2 planning begins next week. Focus areas: retention and activation.",
  },
}

custom - Custom Card

Renders any React component inside a standard dashboard card shell. The card handles all sizing, border, and scroll behaviour — your component just needs to render its own content and can assume it has full width and height available.

import { MyCustomWidget } from "./MyCustomWidget";

{
  id: "my-widget",
  type: "custom",
  size: "medium",
  component: MyCustomWidget,
  props: {
    userId: "abc123",
    showControls: true,
  },
}

The underlying CustomCard wraps your component like this:

interface Props {
  component: React.ComponentType<any>;
  props?: Record<string, unknown>;
}

export const CustomCard: React.FC<Props> = ({
  component: Component,
  props,
}) => (
  <div style={{ width: "100%", height: "100%", overflow: "auto" }}>
    <Component {...(props ?? {})} />
  </div>
);

Your component receives props spread directly as React props. The outer div is width: 100%, height: 100% with overflow: auto, so scrollable content works without any extra wrapper in your component. Any React component with no required props (or with all required props covered by the props field) can be dropped in. This is useful for embedding third-party charts, maps, custom forms, or any one-off widget that doesn't fit the standard types.

Theming

Pass a theme object to <Dashboard> to customise the visual style:

<Dashboard
  items={items}
  theme={{
    primaryColor: "#7c3aed", // Accent / interactive color
    backgroundColor: "#0f172a", // Dashboard background
    cardBackground: "#1e293b", // Widget card background
    borderRadius: "16px", // Card corner radius
    textColor: "#f1f5f9", // Primary text
    mutedColor: "#94a3b8", // Secondary / muted text
    borderColor: "#334155", // Card borders
    fontFamily: "'DM Sans', sans-serif",
  }}
/>

Theme reference

DashboardTheme props:

| Prop | Default | Description | | ----------------- | ----------------------- | -------------------------------------------- | | primaryColor | #6366f1 | Accent color for highlights, buttons, charts | | backgroundColor | #f4f5f7 | Dashboard canvas background | | cardBackground | #ffffff | Card surface background | | borderRadius | 12px | Card border radius | | textColor | #111827 | Primary text color | | mutedColor | #6b7280 | Muted / secondary text | | borderColor | #e5e7eb | Card and divider borders | | fontFamily | system-ui, sans-serif | Font stack |

Dark theme

const darkTheme = {
  primaryColor: "#818cf8",
  backgroundColor: "#0f172a",
  cardBackground: "#1e293b",
  borderRadius: "14px",
  textColor: "#f1f5f9",
  mutedColor: "#94a3b8",
  borderColor: "#334155",
};

Teal/mint theme

const tealTheme = {
  primaryColor: "#14b8a6",
  backgroundColor: "#f0fdfa",
  cardBackground: "#ffffff",
  borderRadius: "8px",
  textColor: "#134e4a",
  mutedColor: "#5eead4",
  borderColor: "#99f6e4",
};

Midnight blue theme

Deep navy background with electric blue accents — ideal for ops dashboards and monitoring tools.

const midnightBlueTheme = {
  primaryColor: "#3b82f6",
  backgroundColor: "#0a0f1e",
  cardBackground: "#111827",
  borderRadius: "10px",
  textColor: "#e2e8f0",
  mutedColor: "#64748b",
  borderColor: "#1e3a5f",
  fontFamily: "'Inter', sans-serif",
};

Warm sand theme

Earthy, neutral tones with amber accents — great for productivity and note-taking apps.

const warmSandTheme = {
  primaryColor: "#d97706",
  backgroundColor: "#fdf6ec",
  cardBackground: "#fffbf5",
  borderRadius: "10px",
  textColor: "#1c1917",
  mutedColor: "#a8a29e",
  borderColor: "#e7d9c8",
  fontFamily: "'Georgia', serif",
};

Forest green theme

Nature-inspired greens with rich, organic feel — suits health, sustainability, or finance dashboards.

const forestGreenTheme = {
  primaryColor: "#16a34a",
  backgroundColor: "#f0fdf4",
  cardBackground: "#ffffff",
  borderRadius: "12px",
  textColor: "#14532d",
  mutedColor: "#6b7280",
  borderColor: "#bbf7d0",
  fontFamily: "'DM Sans', sans-serif",
};

Rose quartz theme

Soft pinks and warm whites — great for lifestyle, wellness, or consumer-facing dashboards.

const roseQuartzTheme = {
  primaryColor: "#e11d48",
  backgroundColor: "#fff1f2",
  cardBackground: "#ffffff",
  borderRadius: "16px",
  textColor: "#881337",
  mutedColor: "#fb7185",
  borderColor: "#fecdd3",
  fontFamily: "'Lato', sans-serif",
};

Slate enterprise theme

High-contrast, professional slate palette — designed for enterprise admin panels and B2B tools.

const slateEnterpriseTheme = {
  primaryColor: "#0284c7",
  backgroundColor: "#f1f5f9",
  cardBackground: "#ffffff",
  borderRadius: "6px",
  textColor: "#0f172a",
  mutedColor: "#64748b",
  borderColor: "#cbd5e1",
  fontFamily: "'system-ui', sans-serif",
};

Sunset orange theme

Warm, energetic gradient palette — eye-catching for marketing dashboards and campaign trackers.

const sunsetOrangeTheme = {
  primaryColor: "#ea580c",
  backgroundColor: "#fff7ed",
  cardBackground: "#ffffff",
  borderRadius: "12px",
  textColor: "#431407",
  mutedColor: "#9a3412",
  borderColor: "#fed7aa",
  fontFamily: "'Nunito', sans-serif",
};

Arctic white theme

Ultra-clean, minimal whites with cool blue accents — perfect for medical, scientific, or data-heavy UIs.

const arcticWhiteTheme = {
  primaryColor: "#0ea5e9",
  backgroundColor: "#f8fafc",
  cardBackground: "#ffffff",
  borderRadius: "8px",
  textColor: "#0c4a6e",
  mutedColor: "#94a3b8",
  borderColor: "#e0f2fe",
  fontFamily: "'IBM Plex Sans', sans-serif",
};

Charcoal ink theme

Dark charcoal with stark white text and vivid cyan accents — ideal for developer tools and terminal-style dashboards.

const charcoalInkTheme = {
  primaryColor: "#06b6d4",
  backgroundColor: "#18181b",
  cardBackground: "#27272a",
  borderRadius: "4px",
  textColor: "#fafafa",
  mutedColor: "#71717a",
  borderColor: "#3f3f46",
  fontFamily: "'JetBrains Mono', monospace",
};

Widget Sizing

Three predefined sizes:

| Size | Grid columns | Row height units | Pixels (approx.) | | -------- | ------------ | ---------------- | ---------------- | | small | 3 | 2 | 160px tall | | medium | 6 | 3 | 240px tall | | large | 12 | 4 | 320px tall |

Custom sizes are also supported:

{
  id: "wide-chart",
  type: "chart",
  customSize: { w: 9, h: 3 }, // 9 columns × 3 row heights
  // ...
}

Drag, Resize & Edit Mode

Click Edit Layout to enter edit mode. In edit mode, all widgets show a drag handle and glow ring. Widgets with resizable: true show a resize handle. Resizing snaps to the nearest predefined size by default; use customSize for free sizing. onLayoutChange fires with the updated layout after drag or resize stops.

<Dashboard
  items={items}
  onLayoutChange={(layout) => {
    saveLayout(layout);
  }}
/>

WidgetLayoutChange payload:

interface WidgetLayoutChange {
  id: string;
  x: number;
  y: number;
  size: "small" | "medium" | "large" | { w: number; h: number };
  draggable: boolean;
  resizable: boolean;
}

Full Example

import React, { useState } from "react";
import { Dashboard, DEFAULT_TEMPLATES } from "react-dashboard-grid";
import type { WidgetItem, WidgetTemplate } from "react-dashboard-grid";

const initialItems: WidgetItem[] = [
  {
    id: "w1",
    type: "stat",
    size: "small",
    data: {
      title: "Revenue",
      value: "$124k",
      change: "+8.2%",
      changeType: "up",
    },
  },
  {
    id: "w2",
    type: "stat",
    size: "small",
    data: {
      title: "Users",
      value: "32,104",
      change: "+1,200",
      changeType: "up",
    },
  },
  {
    id: "w3",
    type: "chart",
    size: "medium",
    data: {
      chartType: "line",
      title: "Revenue Over Time",
      data: [
        { label: "Jan", value: 42000 },
        { label: "Feb", value: 58000 },
        { label: "Mar", value: 51000 },
        { label: "Apr", value: 74000 },
        { label: "May", value: 69000 },
        { label: "Jun", value: 91000 },
      ],
    },
  },
  {
    id: "w4",
    type: "gauge",
    size: "small",
    data: { title: "NPS Score", value: "72", percentage: 72 },
  },
];

const myTemplates: WidgetTemplate[] = [
  ...DEFAULT_TEMPLATES,
  {
    key: "mrr-stat",
    label: "MRR Tracker",
    description: "Monthly recurring revenue with goal progress.",
    component: {
      type: "stat",
      size: "small",
      data: { title: "MRR", value: "$0", changeType: "neutral" },
    },
  },
];

export default function App() {
  return (
    <Dashboard
      title="Analytics Dashboard"
      subtitle="Real-time product metrics"
      items={initialItems}
      widgetTemplates={myTemplates}
      theme={{
        primaryColor: "#6366f1",
        backgroundColor: "#f8fafc",
        cardBackground: "#ffffff",
        borderRadius: "12px",
      }}
      onLayoutChange={(layout) => console.log("Layout changed:", layout)}
      onAddWidget={() => console.log("Widget added")}
    />
  );
}

TypeScript Exports

import {
  // Components
  Dashboard,
  AddWidgetModal,
  DEFAULT_TEMPLATES,

  // Types
  type DashboardProps,
  type WidgetItem,
  type WidgetTemplate,
  type AddWidgetModalProps,
  type DashboardTheme,
  type DashboardAction,
  type WidgetLayoutChange,

  // Widget data types
  type StatData,
  type ChartData,
  type ListData,
  type ListItem,
  type TextData,
  type GaugeData,
  type ProfileData,
  type SplitStatData,
  type TableData,
  type TableColumn,
  type CalendarData,
  type CalendarEvent,
  type SplitStatSegment,

  // Utilities
  WIDGET_SIZES,
  type WidgetSize,
} from "react-dashboard-grid";

FAQ

Q: How do I pre-populate the modal with my product's specific widgets?

Pass widgetTemplates to <Dashboard> or <AddWidgetModal>. Each template includes the full, pre-configured widget data — no extra configuration step for the user.

Q: Can I disable the modal and handle "Add Widget" myself?

Yes. Pass showAddWidget={false} and use your own button that calls your own state logic. Or use onAddWidget as a callback and build your own flow.

Q: Can I have non-snapping, freely resizable widgets?

Yes. Set resizable: true and customSize: { w, h } on the widget. This bypasses snap-to-size and allows free resizing within minW/maxW/minH/maxH bounds.

Q: How do I persist the layout?

Use onLayoutChange — it fires after every drag or resize stop with a clean WidgetLayoutChange[] payload. Save this to your backend and pass it back via items[n].layout.

Q: Can I render completely custom content in a widget?

Yes. Use type: "custom" and pass any React component via the component field. The component receives your props object spread as React props and is wrapped in a full-bleed, scrollable container.