DuckVizBeta
Guides

Embedding Dashboards

Embed interactive DuckViz dashboards in any React application.

The <Dashboard> component from @duckviz/dashboard renders a draggable grid of charts powered by DuckDB SQL queries. This guide shows how to embed it in your app.

Basic setup

import { DuckvizDBProvider, useDuckDB } from "@duckviz/db";
import { Dashboard, type DashboardConfig } from "@duckviz/dashboard";
import { DuckvizThemeProvider } from "@duckviz/ui";

function App() {
  return (
    <DuckvizThemeProvider preset="terracotta" mode="dark">
      <DuckvizDBProvider persistence>
        <DataLoader />
      </DuckvizDBProvider>
    </DuckvizThemeProvider>
  );
}

Building a DashboardConfig

A dashboard config describes what widgets to render and how to lay them out:

const config: DashboardConfig = {
  name: "Sales Overview",
  widgets: [
    {
      id: "revenue-by-category",
      type: "bar",
      title: "Revenue by Category",
      description: "Top 10 categories by total revenue",
      dataKey: `
        SELECT category, SUM(amount) AS value
        FROM t_sales
        GROUP BY category
        ORDER BY value DESC
        LIMIT 10
      `,
      layout: { x: 0, y: 0, w: 6, h: 8 },
    },
    {
      id: "monthly-trend",
      type: "line",
      title: "Monthly Revenue",
      description: "Revenue trend over the past 12 months",
      dataKey: `
        SELECT strftime(date, '%Y-%m') AS category, SUM(amount) AS value
        FROM t_sales
        GROUP BY 1
        ORDER BY 1
      `,
      layout: { x: 6, y: 0, w: 6, h: 8 },
    },
    {
      id: "region-distribution",
      type: "pie",
      title: "Revenue by Region",
      description: "Distribution across regions",
      dataKey: `
        SELECT region AS category, SUM(amount) AS value
        FROM t_sales
        GROUP BY region
      `,
      layout: { x: 0, y: 8, w: 4, h: 8 },
    },
  ],
};

Layout coordinates

Each widget has a layout with grid coordinates:

  • x — column position (0–11 in a 12-column grid)
  • y — row position (0-based)
  • w — width in columns
  • h — height in grid rows (each row is 30px by default)

If no layout is specified, widgets are auto-arranged.

Rendering the dashboard

function SalesDashboard() {
  return (
    <div style={{ height: "100vh", padding: 16 }}>
      <Dashboard config={config} />
    </div>
  );
}

The Dashboard auto-wires to the nearest DuckvizDBProvider to execute SQL queries. No manual data fetching is needed.

Auto-ingesting data

If your rows come from an API (not a user-uploaded file), pass them as datasets and Dashboard will ingest into DuckDB before rendering widgets:

import type { DuckvizDataset } from "@duckviz/db";

const datasets: DuckvizDataset[] = [
  { name: "Sales", data: salesRows, tableName: "t_sales" },
];

<Dashboard config={config} datasets={datasets} />

The widget SQL queries reference the derived table names (t_sales here). Tables are dropped on unmount unless you pass dropTablesOnUnmount={false}.

Using widgets from Explorer

When users generate widgets in the Explorer, convert them to WidgetConfig:

import type { RecommendedWidget } from "@duckviz/explorer";
import type { DashboardConfig, WidgetConfig } from "@duckviz/dashboard";

function buildConfig(widgets: RecommendedWidget[]): DashboardConfig {
  return {
    name: "My Dashboard",
    widgets: widgets.map((w, i) => ({
      id: `w-${i}`,
      type: w.type,
      title: w.title,
      description: w.description,
      dataKey: w.duckdbQuery,
      config: w.config,
    })),
  };
}

Custom widget actions

Add toolbar buttons (refresh, delete, etc.) to each widget:

<Dashboard
  config={config}
  renderWidgetActions={(ctx) => (
    <div style={{ display: "flex", gap: 4 }}>
      <button onClick={() => ctx.refresh()} title="Refresh">

      </button>
      <button onClick={() => removeWidget(ctx.widgetId)} title="Delete">

      </button>
    </div>
  )}
/>

Forcing a layout rebuild

When you add or remove widgets, bump the layoutVersion prop to trigger a clean layout rebuild:

const [layoutVersion, setLayoutVersion] = useState(0);

function addWidget(widget: WidgetConfig) {
  setWidgets((prev) => [...prev, widget]);
  setLayoutVersion((v) => v + 1); // Force rebuild
}

<Dashboard config={config} layoutVersion={layoutVersion} />

Custom empty state

Show a custom message when the dashboard has no widgets:

<Dashboard
  config={{ ...config, widgets: [] }}
  emptyState={
    <div style={{ textAlign: "center", padding: "4rem" }}>
      <h3>No widgets yet</h3>
      <p>Go to the Explorer and click + on a chart to add it here.</p>
    </div>
  }
/>

Grid configuration

Customize the grid layout:

const config: DashboardConfig = {
  widgets: [...],
  grid: {
    cols: 12,           // Number of columns (default: 12)
    rowHeight: 30,      // Pixel height per grid row (default: 30)
    margin: [8, 8],     // Gap between widgets [horizontal, vertical] (default: [8, 8])
  },
};

Theming

The Dashboard reads CSS custom properties from DuckvizThemeProvider. You can also pass a ThemeConfig directly:

<Dashboard
  config={{
    ...config,
    theme: {
      mode: "dark",
      variables: {
        "--app-surface-primary": "#1a1a2e",
        "--app-text-primary": "#e0e0e0",
      },
    },
  }}
/>

See the Custom Themes guide for the full list of theme variables.