# Exploded UI: Dashboard

> A dashboard comes apart into its shell, sidebar, live cards, command menu and a teammate's cursor, walks through each one, then lands back as the screenshot it started as. Hover a layer once it's open, or click a label to jump to it.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/exploded-ui-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/exploded-ui-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/exploded-ui-demo-light-027d00372eaf8a5b.png
- Registry item: https://keyframeui.com/r/exploded-ui-demo.json
- Categories: scroll, 3d, showcase
- Listed: 2026-10-14, source last changed 2026-10-15

## Install

```bash
npx shadcn@latest add https://keyframeui.com/r/exploded-ui-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/exploded-ui.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/exploded-ui#exploded-ui-demo

## Files

### `registry/demos/exploded-ui-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { ExplodedUI } from "@/components/ui/exploded-ui";

// Everything inside the screenshot is sized off the plane's width (Tailwind's
// spacing, text and radius scales are set in container units), so it scales
// like an image while staying real, theme-aware HTML.
const screen =
  "[--spacing:0.4cqw] [--text-xs:1.2cqw] [--text-sm:1.4cqw] [--text-base:1.6cqw] [--text-lg:1.9cqw] [--text-2xl:2.6cqw] [--radius-md:0.6cqw] [--radius-lg:0.8cqw] [--radius-xl:1.2cqw]";

// For a full-page section, drop the scroll box, `containerRef` and
// `stageClassName`: the stage fills the window and sticks while the page scrolls.
export function ExplodedUIDemo() {
  const scrollRef = React.useRef<HTMLDivElement>(null);

  return (
    <div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50">
      <div ref={scrollRef} className="relative h-[var(--demo-height,36rem)] overflow-y-auto overscroll-contain">
        <header className="mx-auto max-w-2xl px-6 pt-16 pb-4 text-center sm:pt-20">
          <p className="text-sm font-medium text-indigo-600 dark:text-indigo-400">Inside Northwind</p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
            Everything your revenue team needs, in one window
          </h2>
          <p className="mt-4 text-zinc-600 dark:text-zinc-400">Scroll to take it apart.</p>
        </header>

        <ExplodedUI
          walkthrough
          containerRef={scrollRef}
          stageClassName="h-[var(--demo-height,36rem)]"
          className={cn("aspect-[16/10] w-full max-w-3xl", screen)}
        >
          <ExplodedUI.Layer label="App shell" description="Navigation, search and settings.">
            <Shell />
          </ExplodedUI.Layer>
          <ExplodedUI.Layer
            label="Sidebar"
            description="Every workspace in one list."
            className="top-[5.6%] bottom-0 left-0 w-[20%]"
          >
            <Sidebar />
          </ExplodedUI.Layer>
          <ExplodedUI.Layer
            label="Live cards"
            description="Metrics update in real time."
            className="top-[17.5%] left-[23%] w-[74%]"
          >
            <MetricCards />
          </ExplodedUI.Layer>
          <ExplodedUI.Layer
            label="Command menu"
            description="Press ⌘K to do anything."
            className="top-[36%] left-[40%] w-[38%]"
          >
            <CommandMenu />
          </ExplodedUI.Layer>
          <ExplodedUI.Layer
            label="Live cursors"
            description="See where your team is working."
            bare
            className="top-[58%] left-[66%] w-[11%]"
          >
            <Cursor />
          </ExplodedUI.Layer>
        </ExplodedUI>

        <section className="mx-auto grid max-w-3xl gap-8 px-6 py-16 sm:grid-cols-3">
          {[
            ["Built for speed", "Every screen loads in under 100ms, even with a million rows."],
            ["Keyboard first", "Anything you can click, you can do from the command menu."],
            ["Live by default", "Numbers, comments and cursors update the moment they change."],
          ].map(([title, body]) => (
            <div key={title}>
              <h3 className="font-medium">{title}</h3>
              <p className="mt-2 text-sm leading-relaxed text-pretty text-zinc-600 dark:text-zinc-400">{body}</p>
            </div>
          ))}
        </section>
      </div>
    </div>
  );
}

const nav = ["Overview", "Pipeline", "Accounts", "Reports"];
const workspaces = [
  ["Acme Corp", "bg-indigo-500"],
  ["Globex", "bg-emerald-500"],
  ["Initech", "bg-amber-500"],
  ["Umbrella", "bg-rose-500"],
];

function Shell() {
  return (
    <div className="flex size-full flex-col bg-white text-xs text-zinc-900 dark:bg-zinc-900 dark:text-zinc-100">
      <div className="flex h-[5.6%] shrink-0 items-center gap-1.5 border-b border-zinc-950/5 px-3 dark:border-white/5">
        {[0, 1, 2].map((dot) => (
          <span key={dot} className="size-2.5 rounded-full bg-zinc-200 dark:bg-zinc-700" />
        ))}
        <span className="mx-auto rounded-md bg-zinc-100 px-3 py-0.5 text-zinc-500 dark:bg-zinc-800">
          app.northwind.io/overview
        </span>
      </div>
      <div className="flex min-h-0 flex-1">
        {/* The sidebar's slot */}
        <div className="w-[20%] shrink-0 bg-zinc-50 dark:bg-zinc-900" />
        <div className="flex min-w-0 flex-1 flex-col gap-4 bg-zinc-50 p-5 dark:bg-zinc-950">
          <div className="flex items-center justify-between">
            <span className="text-lg font-semibold tracking-tight">Overview</span>
            <div className="flex items-center gap-2 text-zinc-500">
              <span className="flex w-40 items-center gap-2 rounded-md bg-white px-2.5 py-1.5 ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/10">
                <SearchIcon />
                Search
                <kbd className="ml-auto font-sans text-zinc-400">⌘K</kbd>
              </span>
              <span className="size-7 rounded-full bg-linear-to-br from-amber-300 to-rose-400" />
            </div>
          </div>
          {/* The cards' slot */}
          <div className="h-[22%] shrink-0" />
          <div className="grid min-h-0 flex-1 grid-cols-[1.6fr_1fr] gap-4">
            <div className="flex flex-col rounded-lg bg-white p-4 ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/5">
              <div className="flex justify-between text-zinc-500">
                <span>Revenue</span>
                <span>Last 12 weeks</span>
              </div>
              <div className="mt-4 flex min-h-0 flex-1 items-end gap-2">
                {[38, 52, 44, 61, 57, 70, 64, 78, 72, 86, 81, 94].map((h, i) => (
                  <span
                    key={i}
                    className={cn("flex-1 rounded-sm", i === 11 ? "bg-indigo-500" : "bg-indigo-500/20 dark:bg-indigo-400/25")}
                    style={{ height: `${h}%` }}
                  />
                ))}
              </div>
            </div>
            <div className="rounded-lg bg-white p-4 ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/5">
              <span className="text-zinc-500">Top accounts</span>
              <ul className="mt-3 space-y-3">
                {workspaces.map(([name, color], i) => (
                  <li key={name} className="flex items-center gap-2">
                    <span className={cn("size-5 rounded-md", color)} />
                    <span className="font-medium">{name}</span>
                    <span className="ml-auto text-zinc-500 tabular-nums">${[48, 36, 22, 17][i]}k</span>
                  </li>
                ))}
              </ul>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function Sidebar() {
  return (
    <div className="flex size-full flex-col gap-0.5 border-r border-zinc-950/5 bg-white p-3 text-xs text-zinc-900 dark:border-white/5 dark:bg-zinc-900 dark:text-zinc-100">
      <div className="mb-4 flex items-center gap-2 px-2 pt-1 text-sm font-semibold">
        <span className="size-5 rounded-md bg-indigo-600" />
        Northwind
      </div>
      {nav.map((item, i) => (
        <span
          key={item}
          className={cn("rounded-md px-2 py-1.5", i === 0 ? "bg-zinc-100 font-medium dark:bg-zinc-800" : "text-zinc-500")}
        >
          {item}
        </span>
      ))}
      <span className="mt-5 mb-1 px-2 text-zinc-400">Workspaces</span>
      {workspaces.map(([name, color]) => (
        <span key={name} className="flex items-center gap-2 px-2 py-1.5 text-zinc-500">
          <span className={cn("size-2 rounded-full", color)} />
          {name}
        </span>
      ))}
    </div>
  );
}

const metrics = [
  { label: "Revenue", value: "$48.2k", delta: "+12.4%", points: "0,30 15,26 30,28 45,18 60,20 75,10 90,12 100,4" },
  { label: "Active users", value: "2,931", delta: "+8.1%", points: "0,24 15,28 30,20 45,22 60,14 75,16 90,8 100,10" },
  { label: "Conversion", value: "3.8%", delta: "+0.6%", points: "0,28 15,22 30,24 45,20 60,22 75,14 90,16 100,8" },
];

function MetricCards() {
  return (
    <div className="grid grid-cols-3 gap-4 bg-zinc-50 text-xs text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
      {metrics.map((metric) => (
        <div key={metric.label} className="rounded-lg bg-white p-4 ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/5">
          <div className="flex items-center justify-between text-zinc-500">
            {metric.label}
            <span className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400">
              <span className="size-1.5 rounded-full bg-current" />
              Live
            </span>
          </div>
          <div className="mt-2 flex items-end justify-between gap-2">
            <span className="text-2xl font-semibold tracking-tight tabular-nums">{metric.value}</span>
            <svg viewBox="0 0 100 34" preserveAspectRatio="none" aria-hidden className="h-8 w-[45%] overflow-visible">
              <polyline
                points={metric.points}
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                vectorEffect="non-scaling-stroke"
                className="text-indigo-500"
              />
            </svg>
          </div>
          <span className="mt-1 block text-emerald-600 tabular-nums dark:text-emerald-400">{metric.delta} this week</span>
        </div>
      ))}
    </div>
  );
}

const commands = [
  { label: "Create invoice", keys: "I" },
  { label: "Invite teammate", keys: "T" },
  { label: "Export report", keys: "E" },
  { label: "Switch workspace", keys: "W" },
];

function CommandMenu() {
  return (
    <div className="overflow-hidden rounded-lg bg-white text-xs text-zinc-900 shadow-2xl ring-1 shadow-zinc-950/20 ring-zinc-950/10 dark:bg-zinc-800 dark:text-zinc-100 dark:ring-white/10">
      <div className="flex items-center gap-2 border-b border-zinc-950/5 px-3 py-2.5 text-sm text-zinc-500 dark:border-white/5">
        <SearchIcon />
        Type a command…
      </div>
      <div className="p-1.5">
        <span className="block px-2 py-1 text-zinc-400">Suggestions</span>
        {commands.map((command, i) => (
          <span
            key={command.label}
            className={cn(
              "flex items-center justify-between rounded-md px-2 py-1.5",
              i === 1 && "bg-indigo-500/10 text-indigo-700 dark:text-indigo-300",
            )}
          >
            {command.label}
            <kbd className="rounded bg-zinc-100 px-1.5 font-sans text-zinc-500 dark:bg-zinc-700 dark:text-zinc-300">
              {command.keys}
            </kbd>
          </span>
        ))}
      </div>
    </div>
  );
}

function Cursor() {
  return (
    <div className="flex flex-col items-start text-xs">
      <svg viewBox="0 0 16 16" aria-hidden className="size-5 drop-shadow-md">
        <path d="M2 1.5 14 7.2l-5.2 1.4L6.6 14Z" className="fill-rose-500 stroke-white" strokeWidth="1.2" strokeLinejoin="round" />
      </svg>
      <span className="mt-0.5 ml-3 rounded-full bg-rose-500 px-2 py-0.5 font-medium text-white shadow-md">Maya</span>
    </div>
  );
}

function SearchIcon() {
  return (
    <svg viewBox="0 0 16 16" aria-hidden className="size-3.5 shrink-0 fill-none stroke-current" strokeWidth="1.6">
      <circle cx="7" cy="7" r="4.5" />
      <path d="m10.5 10.5 3 3" strokeLinecap="round" />
    </svg>
  );
}
```
