Skip to main content
Create customizable dashboards with drag-and-drop reordering, flexible grid layouts, and automatic state persistence. Perfect for building user-configurable interfaces.

Installation

npx shadcn@latest add https://rigidui.com/r/draggable-dashboard.json

Usage

import DraggableDashboard, { DraggableWrapper } from "@/components/draggable-dashboard"

export default function MyDashboard() {
  return (
    <div className="w-full max-w-4xl mx-auto">
      <DraggableDashboard
        showLockToggle={true}
        showHandles={true}
        gridCols={3}
        gap={4}
        defaultLocked={false}
        persistenceKey="my-dashboard"
      >
        <DraggableWrapper id="revenue" gridSize={{ cols: 1, rows: 1 }}>
          <RevenueCard />
        </DraggableWrapper>

        <DraggableWrapper id="users" gridSize={{ cols: 1, rows: 1 }}>
          <UsersCard />
        </DraggableWrapper>

        <DraggableWrapper id="sales" gridSize={{ cols: 1, rows: 1 }}>
          <SalesCard />
        </DraggableWrapper>

        <DraggableWrapper id="chart" gridSize={{ cols: 2, rows: 1 }}>
          <SimpleChart />
        </DraggableWrapper>
      </DraggableDashboard>
    </div>
  )
}

Features

Drag & Drop

Intuitive drag and drop interface allows users to reorder dashboard components easily.

Grid Layout

Responsive grid system with customizable columns and flexible item sizing.

Lock/Unlock

Toggle between edit and view modes to prevent accidental changes to the layout.

Flexible Sizing

Components can span multiple columns and rows to create complex dashboard layouts.

State Persistence

Dashboard layout is automatically saved to localStorage and persists across sessions.

Responsive Design

Automatically adapts grid columns based on screen size (1 col on mobile, 2 on tablet, custom on desktop).

API Reference

DraggableDashboard

children
ReactNode
required
The draggable wrapper components to be rendered in the dashboard
className
string
Additional class names for styling the dashboard container
showLockToggle
boolean
default:"true"
Whether to show the lock/unlock toggle switch
showHandles
boolean
default:"true"
Whether to show drag handles on items
gridCols
number
default:"3"
Number of grid columns for the dashboard layout (on desktop)
gap
number
default:"6"
Gap between grid items in Tailwind spacing units (1-12)
defaultLocked
boolean
default:"false"
Whether the dashboard should be locked by default
onOrderChange
(newOrder: string[]) => void
Callback function called when item order changes
persistenceKey
string
default:"'draggable-dashboard-order'"
Unique key for localStorage to persist item order across sessions

DraggableWrapper

id
string
required
Unique identifier for the draggable item
children
ReactNode
required
The content to be wrapped and made draggable
gridSize
{ cols: number, rows: number }
default:"{ cols: 1, rows: 1 }"
Size of the item in grid columns and rows
className
string
Additional class names for styling the wrapper

Examples

Analytics Dashboard

import DraggableDashboard, { DraggableWrapper } from "@/components/draggable-dashboard"
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"

function AnalyticsDashboard() {
  return (
    <DraggableDashboard gridCols={4} gap={4}>
      <DraggableWrapper id="total-users" gridSize={{ cols: 1, rows: 1 }}>
        <Card>
          <CardHeader>
            <CardTitle>Total Users</CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-3xl font-bold">12,543</p>
          </CardContent>
        </Card>
      </DraggableWrapper>

      <DraggableWrapper id="revenue" gridSize={{ cols: 1, rows: 1 }}>
        <Card>
          <CardHeader>
            <CardTitle>Revenue</CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-3xl font-bold">$45,231</p>
          </CardContent>
        </Card>
      </DraggableWrapper>

      <DraggableWrapper id="chart" gridSize={{ cols: 2, rows: 2 }}>
        <Card className="h-full">
          <CardHeader>
            <CardTitle>Analytics Chart</CardTitle>
          </CardHeader>
          <CardContent>
            <LineChart data={data} />
          </CardContent>
        </Card>
      </DraggableWrapper>
    </DraggableDashboard>
  )
}

User Customizable Dashboard

import { useState, useEffect } from "react"
import DraggableDashboard, { DraggableWrapper } from "@/components/draggable-dashboard"

function UserDashboard({ userId }) {
  const [order, setOrder] = useState<string[]>([])

  const handleOrderChange = async (newOrder: string[]) => {
    setOrder(newOrder)
    // Save to backend
    await fetch(`/api/users/${userId}/dashboard`, {
      method: 'POST',
      body: JSON.stringify({ order: newOrder })
    })
  }

  return (
    <DraggableDashboard
      persistenceKey={`dashboard-${userId}`}
      onOrderChange={handleOrderChange}
      showLockToggle={true}
      gridCols={3}
    >
      <DraggableWrapper id="welcome">
        <WelcomeWidget />
      </DraggableWrapper>
      
      <DraggableWrapper id="tasks">
        <TasksWidget />
      </DraggableWrapper>
      
      <DraggableWrapper id="calendar">
        <CalendarWidget />
      </DraggableWrapper>
    </DraggableDashboard>
  )
}
The dashboard automatically saves the item order to localStorage using the persistenceKey. Users’ custom layouts will persist across page reloads and browser sessions.
The component is fully responsive and adjusts the number of grid columns based on screen size: 1 column on mobile, 2 on tablets, and your specified gridCols on desktop (1024px+).

Build docs developers (and LLMs) love