diff --git a/uhepp-js/src/components/Breadcrumbs.jsx b/uhepp-js/src/components/Breadcrumbs.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..be582a5dbf32b5d61f07cbb45de3d1b14918a9c0
--- /dev/null
+++ b/uhepp-js/src/components/Breadcrumbs.jsx
@@ -0,0 +1,19 @@
+
+import React from "react"
+
+/**
+ * Render a condensed version of the breadcumbs for the dasboard page.
+ * 
+ * The content property has to be a list of (display text, url) pairs.
+ */
+const Breadcrumbs = ({content}) => (
+    <nav aria-label="breadcrumb" className="d-none d-lg-block small">
+      <ol className="breadcrumb my-2 bg-white p-0">
+        { content.map(([text, url], i) =>
+            <li key={i} className="breadcrumb-item"><a href={url}>{text}</a></li>
+        )}
+      </ol>
+    </nav>
+)
+
+export default Breadcrumbs
diff --git a/uhepp-js/src/components/Dashboard.jsx b/uhepp-js/src/components/Dashboard.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..196773e0e8bac4e5d8f481fb3a4fdc3a75df6265
--- /dev/null
+++ b/uhepp-js/src/components/Dashboard.jsx
@@ -0,0 +1,41 @@
+
+import React from "react"
+import VisibilityIcon from "./VisibilityIcon.jsx"
+import Breadcrumbs from "./Breadcrumbs.jsx"
+import ViewDate from "./ViewDate.jsx"
+import Uhepp from "./Uhepp.jsx"
+
+/**
+ * Main UX component to show plots including the its edit menu
+ *
+ * The dashboard provides ux components to view and manipulate plots. The
+ * dashboard encompasses columns for a left sidebar with the modification
+ * menu, the main column with the actual plot and a right sidebar with
+ * metadata about the plot. The dashboard expects to be mounted within a
+ * bootstrap container component.
+ */
+const Dashboard = ({title, visibility, breadcrumbs, viewdate, uuid}) => (
+<div className="row">
+  <div className="col-md-4 col-lg-3 sidebar bg-light shadow"> 
+    Menu
+  </div>
+
+  <div className="col-md-6 col-lg-7"> 
+    <Breadcrumbs content={breadcrumbs} />
+    
+    <h1 className="mb-0">
+        { title } <VisibilityIcon level={visibility} />
+    </h1>
+
+    <ViewDate data={viewdate} />
+    
+   <Uhepp width={555} height={400} uuid={uuid} />
+  </div>
+
+  <div className="col-md-2 sidebar border-left">
+    Metadata
+  </div>
+</div>
+)
+
+export default Dashboard
diff --git a/uhepp-js/src/components/ViewDate.jsx b/uhepp-js/src/components/ViewDate.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..442e66fb1e2e646ec8238146f23682a3ce33e63e
--- /dev/null
+++ b/uhepp-js/src/components/ViewDate.jsx
@@ -0,0 +1,18 @@
+
+import React from "react"
+
+/**
+ * Small helper component to render the view count and the uploaded date
+ * 
+ * Argument need to pre-rendered pluralized and humanized strings.
+ */
+const ViewDate = ({data}) => (
+    <div className="d-none d-lg-inline text-muted small">
+        <i className="fas fa-eye"></i> { data.viewstring } {' '}
+        <span title={data.uploaded}>
+            <i className="fas fa-clock"></i> { data.datestring }
+        </span>
+    </div>
+)
+
+export default ViewDate
diff --git a/uhepp-js/src/components/VisibilityIcon.jsx b/uhepp-js/src/components/VisibilityIcon.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..edee9ea17ab7dbdf2aeabe332128b11cc1635d59
--- /dev/null
+++ b/uhepp-js/src/components/VisibilityIcon.jsx
@@ -0,0 +1,17 @@
+
+import React from "react"
+import classNames from "classnames"
+
+/**
+ * Produces as FontAwesome <i> elemnt based on the given visibility level
+ */
+const VisibilityIcon = ({level}) => (
+    <i className={
+        classNames("text-muted", "fas",
+                  {"fa-globe-europe": level >= 30,
+                   "fa-shield-alt": level < 30 && level >= 20,
+                   "fa-lock": level < 20 && level >= 10})}>
+    </i>
+)
+
+export default VisibilityIcon
diff --git a/uhepp-js/src/index.js b/uhepp-js/src/index.js
index 725bae53e671cd6510db76ab672615397a61e84a..80b68ee6ff288f18e1ab4fddf1ac003c48f12e9d 100644
--- a/uhepp-js/src/index.js
+++ b/uhepp-js/src/index.js
@@ -5,6 +5,7 @@ import './common.scss'
 import 'bootstrap'
 
 import Uhepp from './components/Uhepp.jsx';
+import Dashboard from './components/Dashboard.jsx';
 
 import React from "react";
 import ReactDOM from "react-dom";
@@ -17,4 +18,5 @@ export const fe = {
   React: React, 
   ReactDOM: ReactDOM,
   Uhepp: Uhepp, 
+  Dashboard: Dashboard,
 };
diff --git a/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html b/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html
index 842b771eab3c1453cab3f37e2387e6f1861fd5b5..40aa7b8ad487b3927da88a62a2250fbe8bd58f66 100644
--- a/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html
+++ b/uhepp_org/uhepp_vault/templates/uhepp_vault/plot_dashboard.html
@@ -8,11 +8,11 @@
 {% block nav-container %}
     <div class="container-fluid">
     <div class="row">
-        <div class="col-md-3 col-lg-2" style="background-color: black">
+        <div class="col-md-4 col-lg-3 shadow" style="background-color: black">
 {% endblock %}
 {% block nav-container-mid %}
         </div>
-        <div class="col-md-9 col-lg-10">
+        <div class="col-md-8 col-lg-9">
 {% endblock %}
 {% block nav-container2 %}
         </div>
@@ -21,49 +21,28 @@
 {% endblock %}
 
 {% block content-fluid %}
-<div class="container-fluid"><div class="row">
-
-  <div class="col-md-3 col-lg-2 sidebar bg-light shadow"> 
-    Menu
-  </div>
-
-  <div class="col-md-7 col-lg-8"> 
-    <nav aria-label="breadcrumb" class="d-none d-lg-block small">
-      <ol class="breadcrumb my-2 bg-white p-0">
-        <li class="breadcrumb-item"><a href="/">Home</a></li>
-        <li class="breadcrumb-item"><a href="{% url 'uhepp_vault:user-detail' plot.collection.owner.username %}">{{ plot.collection.owner }}</a></li>
-        <li class="breadcrumb-item"><a href="{% url 'uhepp_vault:collection-detail' plot.collection.id %}">{{ plot.collection.title }}</a></li>
-        <li class="breadcrumb-item active"><a href="{% url 'uhepp_vault:plot-detail' plot.uuid %}">{{ plot }}</a></li>
-      </ol>
-    </nav>
-    
-    <h1 class="mb-0">
-        {{ plot }}
-        <i class="text-muted fas {% if plot.collection.visibility >= 30 %} fa-globe-europe
-        {% elif plot.collection.visibility >= 20 %} fa-shield-alt
-        {% elif plot.collection.visibility >= 10 %} fa-lock
-        {% endif %}"></i>
-    </h1>
-    
-    <div class="d-none d-lg-inline text-muted small">
-        <i class="fas fa-eye"></i>
-        {{ plot.view_count }} view{{ plot.view_count|pluralize }},
-        <span title="{{ plot.uploaded|date:"Y-m-d H:m" }}">
-            <i class="fas fa-clock"></i>
-            {{ plot.uploaded|naturaltime }}
-        </span>
+<div class="container-fluid" id="app-root">
+  <div class="row">
+    <div class="col-12">
+      <div class="d-flex justify-content-center my-5">
+        <div class="spinner-border text-primary" role="status">
+          <span class="sr-only">Loading...</span>
+        </div>
+      </div>
+      <div class="text-center">Loading dashboard...</div>
     </div>
   </div>
-
-  <div class="col-md-2 sidebar shadow-sm">
-    Metadata
-  </div>
-
-</div></div>
+</div>
 {% endblock %}
 
 {% block loadscript %}
+{{ dashboard_props|json_script:"dashboard-props" }}
 <script>
+var props = JSON.parse(document.getElementById('dashboard-props').textContent);
+uhepp.fe.ReactDOM.render(
+  uhepp.fe.React.createElement(uhepp.fe.Dashboard, props),
+  document.getElementById('app-root')
+);
 /*uhepp.fe.ReactDOM.render(
   uhepp.fe.React.createElement(uhepp.fe.Uhepp, {
     width: "555",
diff --git a/uhepp_org/uhepp_vault/views.py b/uhepp_org/uhepp_vault/views.py
index 024d3f71083c20500286cfd5b4cdaf4cb32233fa..905b71cfd48b15cfbdbc3a4c189e42a7bf432699 100644
--- a/uhepp_org/uhepp_vault/views.py
+++ b/uhepp_org/uhepp_vault/views.py
@@ -12,6 +12,8 @@ from django.conf import settings
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.models import User, Group
 from django.utils.translation import gettext_lazy as _
+from django.template.defaultfilters import pluralize
+from django.contrib.humanize.templatetags.humanize import naturaltime
 
 from uhepp_api.models import Token
 from uhepp_api.views import MaskedRelatedMixin
@@ -250,6 +252,52 @@ class PlotDetail(generic.DetailView):
                 raise original
             return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
 
+    def get_breadcrumbs_map(self):
+        """Return a list of tuples with display text and link url"""
+        plot = self.object
+        return [
+            ("Home", "/"),
+            (str(plot.collection.owner),
+                reverse("uhepp_vault:user-detail", args=[plot.collection.owner.username])),
+            (plot.collection.title,
+                reverse("uhepp_vault:collection-detail", args=[plot.collection.id])),
+            (str(plot),
+                reverse("uhepp_vault:collection-detail", args=[plot.uuid])),
+        ]
+
+    def get_viewstring(self):
+        """Return the human-readable, pluralized view count string"""
+        view_count = self.object.view_count()
+        plural = pluralize(view_count)
+        return f"{view_count:d} view{plural:s}"
+
+    def get_datestring(self):
+        """Return the human-readable creation time string"""
+        return naturaltime(self.object.uploaded)
+
+    def get_viewdate(self):
+        """Build sub-dict with rendered view coutn and creation date strs"""
+        return {
+            "viewstring": self.get_viewstring(),
+            "datestring": self.get_datestring(),
+            "uploaded": self.object.uploaded.strftime("%Y-%m-%d %H:%m"),
+        }
+
+
+    def get_context_data(self, **kwargs):
+        """Add dashboard props to context"""
+        context = {}
+        context["dashboard_props"] = {
+            "title": self.object.title,
+            "visibility": self.object.collection.visibility,
+            "breadcrumbs": self.get_breadcrumbs_map(),
+            "viewdate": self.get_viewdate(),
+            "uuid": self.object.uuid,
+        }
+        context.update(kwargs)
+        return super().get_context_data(**context)
+
+
 class PlotDeleteView(LoginRequiredMixin, generic.DeleteView):
     model = Plot
     slug_field = 'uuid'